Merge branch 'develop' into feature/files-in-use

This commit is contained in:
Armin Schrenk
2025-11-21 10:15:24 +01:00
128 changed files with 3584 additions and 384 deletions
+4
View File
@@ -20,6 +20,10 @@
Translations are not managed directly in this repository. Instead, we use [Crowdin](https://translate.cryptomator.org/), which automatically synchronizes translations with this repository. If you want to help us with translations, please visit our translation project on Crowdin.
## Use of Generative AI
AI tools may assist your work, but every contribution must be fully understood, reviewed, and tested by you. Only submit changes you can clearly explain and justify. Unverified or low-quality AI output that wastes our time and resources will be closed without further review.
## Code of Conduct
Help us keep Cryptomator open and inclusive. Please read and follow our [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md).
@@ -0,0 +1,76 @@
name: 'Windows Code Signing'
description: 'Sign files on Windows with the Azure Trusted Signing'
inputs:
base-dir:
description: 'Absolute path to the base directory to search for files'
required: true
recursive:
description: 'Whether to search recursively in subdirectories'
required: false
default: 'false'
file-extensions:
description: 'List of file extensions to sign, separated by comma'
required: true
default: 'exe,dll,ps1'
description:
description: 'Signature description'
required: true
default: 'Cryptomator'
url:
description: 'Signature URL'
required: false
default: 'https://cryptomator.org'
append-signature:
description: 'Whether to append the signature to existing signatures'
required: false
default: 'false'
tenant-id:
description: 'Azure Tenant ID'
required: true
client-id:
description: 'Azure Client ID'
required: true
client-secret:
description: 'Azure Client Secret'
required: true
runs:
using: 'composite'
steps:
- name: Generate, mask, and output the input secrets
id: set-secrets
run: |
echo "::add-mask::${{ inputs.tenant-id }}"
echo "::add-mask::${{ inputs.client-id }}"
echo "::add-mask::${{ inputs.client-secret }}"
echo "tenant-id=${{ inputs.tenant-id }}" >> "$GITHUB_OUTPUT"
echo "client-id=${{ inputs.client-id }}" >> "$GITHUB_OUTPUT"
echo "client-secret=${{ inputs.client-secret }}" >> "$GITHUB_OUTPUT"
shell: bash
- name: Sign DLLs with Azure Trusted Signing
uses: azure/trusted-signing-action@fc390cf8ed0f14e248a542af1d838388a47c7a7c # v0.5.10
with:
files-folder: ${{ inputs.base-dir }}
files-folder-filter: ${{ inputs.file-extensions }}
files-folder-recurse: ${{ inputs.recursive }}
append-signature: ${{ inputs.append-signature }}
description: ${{ inputs.description }}
description-url: ${{ inputs.url }}
azure-tenant-id: ${{ steps.set-secrets.outputs.tenant-id }}
azure-client-id: ${{ steps.set-secrets.outputs.client-id }}
azure-client-secret: ${{ steps.set-secrets.outputs.client-secret }}
trusted-signing-account-name: cryptomatorSigning
certificate-profile-name: production
endpoint: https://weu.codesigning.azure.net/
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
exclude-environment-credential: false
exclude-workload-identity-credential: true
exclude-managed-identity-credential: true
exclude-shared-token-cache-credential: true
exclude-visual-studio-credential: true
exclude-visual-studio-code-credential: true
exclude-azure-cli-credential: true
exclude-azure-powershell-credential: true
exclude-azure-developer-cli-credential: true
exclude-interactive-browser-credential: true
+2 -2
View File
@@ -19,7 +19,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '24.0.1+9'
JAVA_VERSION: '25.0.1+8.0.LTS'
jobs:
get-version:
@@ -42,7 +42,7 @@ jobs:
- os: ubuntu-24.04-arm
appimage-suffix: aarch64
openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
openjfx-sha: '951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
openjfx-sha: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup Java
+1 -1
View File
@@ -11,7 +11,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: 24
JAVA_VERSION: 25
defaults:
run:
+2 -2
View File
@@ -6,7 +6,7 @@ on:
workflow_dispatch:
env:
JDK_VERSION: '24.0.1+9'
JDK_VERSION: '25.0.1+8.0.LTS'
JDK_VENDOR: temurin
RUNTIME_VERSION_HELPER: >
public class Test {
@@ -23,7 +23,7 @@ jobs:
JDK_MAJOR_VERSION: 'toBeFilled'
steps:
- name: Determine current major version
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV"
run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,2) >> "$env:GITHUB_ENV"
shell: pwsh
- name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }}
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
+7 -6
View File
@@ -23,13 +23,12 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '24.0.1+9'
COFFEELIBS_JDK: 24
COFFEELIBS_JDK_VERSION: '24.0.1+9-0ppa3'
JAVA_VERSION: '25.0.1+8.0.LTS'
DEB_BUILD_DEPENDS: 'debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1'
OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip'
OPENJFX_JMODS_AMD64_HASH: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e'
OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip'
OPENJFX_JMODS_AARCH64_HASH: '951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1'
OPENJFX_JMODS_AARCH64_HASH: '9ad4ca7b769ca4ee6419f1e99143dd6ff812f8be4fddb46a7d7cacbeea148af4'
jobs:
get-version:
@@ -55,9 +54,11 @@ jobs:
fi
- name: Install build tools
run: |
sudo add-apt-repository ppa:coffeelibs/openjdk
sudo apt-get update
sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }}
sudo apt-get install devscripts dput
sudo apt-get satisfy "${DEB_BUILD_DEPENDS}"
env:
DEB_BUILD_DEPENDS: ${{ env.DEB_BUILD_DEPENDS }}
- name: Setup Java
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
with:
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
with:
runner-os: 'ubuntu-latest'
java-distribution: 'temurin'
java-version: 24
java-version: 25
secrets:
nvd-api-key: ${{ secrets.NVD_API_KEY }}
ossindex-username: ${{ secrets.OSSINDEX_USERNAME }}
+1 -1
View File
@@ -23,7 +23,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: 24
JAVA_VERSION: 25
jobs:
determine-version:
+1 -1
View File
@@ -24,7 +24,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '24.0.1+9'
JAVA_VERSION: '25.0.1+8.0.LTS'
jobs:
get-version:
+1 -1
View File
@@ -22,7 +22,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: '24.0.1+9'
JAVA_VERSION: '25.0.1+8.0.LTS'
jobs:
get-version:
+1 -1
View File
@@ -5,7 +5,7 @@ on:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: 24
JAVA_VERSION: 25
defaults:
run:
+1 -1
View File
@@ -12,7 +12,7 @@ defaults:
env:
JAVA_DIST: 'temurin'
JAVA_VERSION: 23
JAVA_VERSION: 25
jobs:
check-preconditions:
+42 -14
View File
@@ -8,10 +8,6 @@ on:
version:
description: 'Version'
required: false
isDebug:
description: 'Build debug version with console output'
type: boolean
default: false
sign:
description: 'Sign binaries'
required: false
@@ -51,8 +47,8 @@ jobs:
include:
- arch: x64
os: windows-latest
java-dist: 'zulu'
java-version: '24.0.1+9'
java-dist: 'zulu' #cannot use temurin, see https://github.com/cryptomator/cryptomator/issues/3824#issuecomment-2829827427
java-version: '25.0.1+8'
java-package: 'jdk'
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -192,6 +188,16 @@ jobs:
New-Item -Path appdir/jpackage-jmod -ItemType Directory
& $env:JAVA_HOME\bin\jmod.exe extract --dir jpackage-jmod "${env:JAVA_HOME}\jmods\jdk.jpackage.jmod"
Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir"
- name: Sign DLLs with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\appdir
recursive: true
append-signature: true
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign DLLs with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
@@ -251,16 +257,16 @@ jobs:
env:
JP_WIXWIZARD_RESOURCES: ${{ github.workspace }}/dist/win/resources # requires abs path, used in resources/main.wxs
JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir
- name: Sign msi with Actalis CodeSigner
- name: Sign MSI with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
uses: ./.github/actions/win-sign-action
with:
base-dir: 'installer'
file-extensions: 'msi'
sign-description: 'Cryptomator Installer'
sign-url: 'https://cryptomator.org'
username: ${{ secrets.WIN_CODESIGN_USERNAME }}
password: ${{ secrets.WIN_CODESIGN_PW }}
base-dir: ${{ github.workspace }}\installer
file-extensions: msi
description: 'Cryptomator Installer'
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Add possible alpha/beta tags and architecture to installer name
run: mv installer/Cryptomator-*.msi Cryptomator-${{ needs.get-version.outputs.semVerStr }}-${{ matrix.arch }}.msi
- name: Create detached GPG signature with key 615D449FE6E6A235
@@ -357,6 +363,17 @@ jobs:
- name: Detach burn engine in preparation to sign
run: >
wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe
- name: Sign WiX burn engine with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\tmp
file-extensions: exe
append-signature: true
description: 'Cryptomator Bundle Installer'
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign burn engine with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
@@ -370,6 +387,17 @@ jobs:
- name: Reattach signed burn engine to installer
run: >
wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe
- name: Sign EXE installer with Azure Trusted Signing
if: inputs.sign || github.event_name == 'release'
uses: ./.github/actions/win-sign-action
with:
base-dir: ${{ github.workspace }}\installer
file-extensions: exe
append-signature: true
description: 'Cryptomator Bundle Installer'
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
client-id: ${{ secrets.AZURE_CLIENT_ID }}
client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Sign installer with Actalis CodeSigner
if: inputs.sign || github.event_name == 'release'
uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version
+11 -10
View File
@@ -14,16 +14,15 @@
<option name="dagger.fastInit" value="enabled" />
<option name="dagger.formatGeneratedSource" value="enabled" />
<processorPath useClasspath="false">
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.55/dagger-compiler-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.55/dagger-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-compiler/2.57.2/dagger-compiler-2.57.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger/2.57.2/dagger-2.57.2.jar" />
<entry name="$MAVEN_REPOSITORY$/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.55/dagger-spi-2.55.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/dagger/dagger-spi/2.57.2/dagger-spi-2.57.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.0.21-1.0.28/symbol-processing-api-2.0.21-1.0.28.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/devtools/ksp/symbol-processing-api/2.1.21-2.0.2/symbol-processing-api-2.1.21-2.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/guava/33.0.0-jre/guava-33.0.0-jre.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
@@ -31,14 +30,16 @@
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.23.0/error_prone_annotations-2.23.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/javapoet/1.13.0/javapoet-1.13.0.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/googlejavaformat/google-java-format/1.5/google-java-format-1.5.jar" />
<entry name="$MAVEN_REPOSITORY$/com/google/errorprone/javac-shaded/9-dev-r4023-3/javac-shaded-9-dev-r4023-3.jar" />
<entry name="$MAVEN_REPOSITORY$/com/squareup/kotlinpoet/1.11.0/kotlinpoet-1.11.0.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar" />
<entry name="$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$MAVEN_REPOSITORY$/net/ltgt/gradle/incap/incap/0.2/incap-0.2.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar" />
<entry name="$MAVEN_REPOSITORY$/org/checkerframework/checker-compat-qual/2.5.3/checker-compat-qual-2.5.3.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-metadata-jvm/2.1.21/kotlin-metadata-jvm-2.1.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.1.21/kotlin-stdlib-2.1.21.jar" />
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
</processorPath>
<module name="cryptomator" />
</profile>
+1 -1
View File
@@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_24" project-jdk-name="25" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="25" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -83,6 +83,9 @@
</content_rating>
<releases>
<release date="2025-11-12" version="1.18.0">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.18.0</url>
</release>
<release date="2025-07-08" version="1.17.1">
<url type="details">https://github.com/cryptomator/cryptomator/releases/1.17.1</url>
</release>
+1 -1
View File
@@ -2,7 +2,7 @@ Source: cryptomator
Maintainer: Cryptobot <releases@cryptomator.org>
Section: utils
Priority: optional
Build-Depends: debhelper (>=10), coffeelibs-jdk-24 (>= 24.0.1+9-0ppa3), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1
Build-Depends: debhelper (>=10), openjdk-25-jdk (>= 25+36), libgtk-3-0 (>= 3.20.0), libxxf86vm1, libgl1
Standards-Version: 4.5.0
Homepage: https://cryptomator.org
Vcs-Git: https://github.com/cryptomator/cryptomator.git
+2 -1
View File
@@ -4,11 +4,12 @@
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
JAVA_HOME = /usr/lib/jvm/java-24-coffeelibs
DEB_BUILD_ARCH ?= $(shell dpkg-architecture -qDEB_BUILD_ARCH)
ifeq ($(DEB_BUILD_ARCH),amd64)
JAVA_HOME = /usr/lib/jvm/java-25-openjdk-amd64
JMODS_PATH = jmods/amd64:${JAVA_HOME}/jmods
else ifeq ($(DEB_BUILD_ARCH),arm64)
JAVA_HOME = /usr/lib/jvm/java-25-openjdk-arm64
JMODS_PATH = jmods/aarch64:${JAVA_HOME}/jmods
endif
+3 -3
View File
@@ -1,6 +1,6 @@
Apache License v2.0|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|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
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
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
+3 -3
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptomator</artifactId>
<version>1.18.0-SNAPSHOT</version>
<version>1.19.0-SNAPSHOT</version>
<name>Cryptomator Desktop App</name>
<organization>
@@ -26,7 +26,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.jdk.version>24</project.jdk.version>
<project.jdk.version>25</project.jdk.version>
<!-- Group IDs of jars that need to stay on the class path for now -->
<!-- remove them, as soon they got modularized or support is dropped (i.e., WebDAV) -->
@@ -62,7 +62,7 @@
<!-- build-time dependencies -->
<jetbrains.annotations.version>26.0.2-1</jetbrains.annotations.version>
<dependency-check.version>12.1.5</dependency-check.version>
<jacoco.version>0.8.13</jacoco.version>
<jacoco.version>0.8.14</jacoco.version>
<license-generator.version>2.7.0</license-generator.version>
<junit-tree-reporter.version>1.4.0</junit-tree-reporter.version>
<mvn-compiler.version>3.14.1</mvn-compiler.version>
@@ -0,0 +1,53 @@
package org.cryptomator.common.recovery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.util.stream.Stream;
import static org.cryptomator.common.Constants.MASTERKEY_BACKUP_SUFFIX;
public final class BackupRestorer {
private static final Logger LOG = LoggerFactory.getLogger(BackupRestorer.class);
private BackupRestorer() {}
public static void restoreIfBackupPresent(Path vaultPath, String filePrefix) {
Path targetFile = vaultPath.resolve(filePrefix);
try (Stream<Path> files = Files.list(vaultPath)) {
files.filter(file -> isFileMatchingPattern(file.getFileName().toString(), filePrefix))
.max((f1, f2) -> {
try {
FileTime time1 = Files.getLastModifiedTime(f1);
FileTime time2 = Files.getLastModifiedTime(f2);
return time1.compareTo(time2);
} catch (IOException e) {
return 0;
}
})
.ifPresent(backupFile -> copyBackupFile(backupFile, targetFile));
} catch (IOException e) {
LOG.info("Unable to restore backup files in '{}'", vaultPath, e);
}
}
private static boolean isFileMatchingPattern(String fileName, String filePrefix) {
return fileName.startsWith(filePrefix) && fileName.endsWith(MASTERKEY_BACKUP_SUFFIX);
}
private static void copyBackupFile(Path backupFile, Path configPath) {
try {
Files.copy(backupFile, configPath, StandardCopyOption.REPLACE_EXISTING);
LOG.debug("Backup restored - file: '{}' path: '{}'", backupFile, configPath);
} catch (IOException e) {
LOG.warn("Unable to copy backup file from '{}' to '{}'", backupFile, configPath, e);
}
}
}
@@ -0,0 +1,33 @@
package org.cryptomator.common.recovery;
import java.io.IOException;
import java.nio.file.Path;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import static org.cryptomator.common.Constants.DEFAULT_KEY_ID;
public final class CryptoFsInitializer {
private CryptoFsInitializer() {}
public static void init(Path recoveryPath,
Masterkey masterkey,
int shorteningThreshold,
CryptorProvider.Scheme scheme) throws IOException, CryptoException {
MasterkeyLoader loader = ignored -> masterkey.copy();
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties //
.cryptoFileSystemProperties() //
.withCipherCombo(scheme) //
.withKeyLoader(loader) //
.withShorteningThreshold(shorteningThreshold) //
.build();
CryptoFileSystemProvider.initialize(recoveryPath, fsProps, DEFAULT_KEY_ID);
}
}
@@ -0,0 +1,101 @@
package org.cryptomator.common.recovery;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.cryptofs.common.Constants.DATA_DIR_NAME;
public final class MasterkeyService {
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyService.class);
private MasterkeyService() {}
public static void recoverFromRecoveryKey(String recoveryKey, RecoveryKeyFactory recoveryKeyFactory, Path recoveryPath, CharSequence newPassword) throws IOException {
recoveryKeyFactory.newMasterkeyFileWithPassphrase(recoveryPath, recoveryKey, newPassword);
}
public static Masterkey load(MasterkeyFileAccess masterkeyFileAccess, Path masterkeyFilePath, CharSequence password) throws IOException {
return masterkeyFileAccess.load(masterkeyFilePath, password);
}
public static CryptorProvider.Scheme validateRecoveryKeyAndDetectCombo(RecoveryKeyFactory recoveryKeyFactory, //
Vault vault, String recoveryKey, //
MasterkeyFileAccess masterkeyFileAccess) throws IOException, CryptoException, NoSuchElementException {
String tmpPass = UUID.randomUUID().toString();
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
Path tempRecoveryPath = recoveryDirectory.getRecoveryPath();
recoverFromRecoveryKey(recoveryKey, recoveryKeyFactory, tempRecoveryPath, tmpPass);
Path masterkeyFilePath = tempRecoveryPath.resolve(MASTERKEY_FILENAME);
try (Masterkey mk = load(masterkeyFileAccess, masterkeyFilePath, tmpPass)) {
return detect(mk, vault.getPath()).orElseThrow();
}
}
}
public static Optional<CryptorProvider.Scheme> detect(Masterkey masterkey, Path vaultPath) {
try (Stream<Path> paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) {
Optional<Path> c9rFile = paths //
.filter(p -> p.toString().endsWith(".c9r")) //
.filter(p -> !p.endsWith("dir.c9r")) //
.findFirst();
if (c9rFile.isEmpty()) {
LOG.info("Unable to detect Crypto scheme: No *.c9r file found in {}", vaultPath);
return Optional.empty();
}
return determineScheme(c9rFile.get(), masterkey);
} catch (IOException e) {
LOG.info("Unable to detect Crypto scheme: Failed to inspect vault", e);
return Optional.empty();
}
}
private static Optional<CryptorProvider.Scheme> determineScheme(Path c9rFile, Masterkey masterkey) {
return Arrays.stream(CryptorProvider.Scheme.values()).filter(scheme -> {
try (Cryptor cryptor = CryptorProvider.forScheme(scheme).provide(masterkey.copy(), SecureRandom.getInstanceStrong())) {
int headerSize = cryptor.fileHeaderCryptor().headerSize();
ByteBuffer headerBuf = ByteBuffer.allocate(headerSize);
try (FileChannel channel = FileChannel.open(c9rFile, StandardOpenOption.READ)) {
channel.read(headerBuf, 0);
}
headerBuf.flip();
cryptor.fileHeaderCryptor().decryptHeader(headerBuf.duplicate());
LOG.debug("Detected Crypto scheme: {}", scheme);
return true;
} catch (IllegalArgumentException | CryptoException e) {
LOG.debug("Could not decrypt with scheme: {}", scheme);
return false;
} catch (IOException | NoSuchAlgorithmException e) {
LOG.warn("Unable to detect Crypto scheme: Failed to decrypt .c9r file", e);
return false;
}
}).findFirst();
}
}
@@ -0,0 +1,10 @@
package org.cryptomator.common.recovery;
public enum RecoveryActionType {
RESTORE_ALL,
RESTORE_MASTERKEY,
RESTORE_VAULT_CONFIG,
RESET_PASSWORD,
SHOW_KEY,
CONVERT_VAULT
}
@@ -0,0 +1,56 @@
package org.cryptomator.common.recovery;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class RecoveryDirectory implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryDirectory.class);
private final Path recoveryPath;
private final Path vaultPath;
private RecoveryDirectory(Path vaultPath, Path recoveryPath) {
this.vaultPath = vaultPath;
this.recoveryPath = recoveryPath;
}
public static RecoveryDirectory create(Path vaultPath) throws IOException {
Path tempDir = Files.createTempDirectory("cryptomator");
return new RecoveryDirectory(vaultPath, tempDir);
}
public void moveRecoveredFile(String file) throws IOException {
Files.move(recoveryPath.resolve(file), vaultPath.resolve(file), StandardCopyOption.REPLACE_EXISTING);
}
private void deleteRecoveryDirectory() {
try (var paths = Files.walk(recoveryPath)) {
paths.sorted(Comparator.reverseOrder()).forEach(p -> {
try {
Files.delete(p);
} catch (IOException e) {
LOG.info("Unable to delete {}. Please delete it manually.", p);
}
});
} catch (IOException e) {
LOG.error("Failed to clean up recovery directory", e);
}
}
@Override
public void close() {
deleteRecoveryDirectory();
}
public Path getRecoveryPath() {
return recoveryPath;
}
}
@@ -0,0 +1,54 @@
package org.cryptomator.common.recovery;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultConfigCache;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.integrations.mount.MountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.ResourceBundle;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
public final class VaultPreparator {
private static final Logger LOG = LoggerFactory.getLogger(VaultPreparator.class);
private VaultPreparator() {}
public static Vault prepareVault(Path selectedDirectory, //
VaultComponent.Factory vaultComponentFactory, //
List<MountService> mountServices, //
ResourceBundle resourceBundle) {
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path.set(selectedDirectory);
if (selectedDirectory.getFileName() != null) {
vaultSettings.displayName.set(selectedDirectory.getFileName().toString());
} else {
vaultSettings.displayName.set(resourceBundle.getString("defaults.vault.vaultName"));
}
var wrapper = new VaultConfigCache(vaultSettings);
Vault vault = vaultComponentFactory.create(vaultSettings, wrapper, LOCKED, null).vault();
try {
VaultListManager.determineVaultState(vault.getPath());
} catch (IOException e) {
LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
}
//due to https://github.com/cryptomator/cryptomator/issues/2880#issuecomment-1680313498
var nameOfWinfspLocalMounter = "org.cryptomator.frontend.fuse.mount.WinFspMountProvider";
if (SystemUtils.IS_OS_WINDOWS && vaultSettings.path.get().toString().contains("Dropbox") && mountServices.stream().anyMatch(s -> s.getClass().getName().equals(nameOfWinfspLocalMounter))) {
vaultSettings.mountService.setValue(nameOfWinfspLocalMounter);
}
return vault;
}
}
@@ -23,7 +23,6 @@ import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.integrations.mount.MountFailedException;
import org.cryptomator.integrations.mount.Mountpoint;
import org.cryptomator.integrations.mount.UnmountFailedException;
@@ -35,7 +34,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
@@ -75,6 +73,7 @@ public class Vault {
private final BooleanBinding missing;
private final BooleanBinding needsMigration;
private final BooleanBinding unknownError;
private final BooleanBinding missingVaultConfig;
private final ObjectBinding<Mountpoint> mountPoint;
private final Mounter mounter;
private final Settings settings;
@@ -103,6 +102,7 @@ public class Vault {
this.processing = Bindings.createBooleanBinding(this::isProcessing, state);
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
this.missing = Bindings.createBooleanBinding(this::isMissing, state);
this.missingVaultConfig = Bindings.createBooleanBinding(this::isMissingVaultConfig, state);
this.needsMigration = Bindings.createBooleanBinding(this::isNeedsMigration, state);
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
@@ -337,6 +337,14 @@ public class Vault {
return state.get() == VaultState.Value.ERROR;
}
public BooleanBinding missingVaultConfigProperty() {
return missingVaultConfig;
}
public boolean isMissingVaultConfig() {
return state.get() == VaultState.Value.VAULT_CONFIG_MISSING || state.get() == VaultState.Value.ALL_MISSING;
}
public ReadOnlyStringProperty displayNameProperty() {
return vaultSettings.displayName;
}
@@ -20,7 +20,7 @@ public class VaultConfigCache {
private final VaultSettings settings;
private final AtomicReference<VaultConfig.UnverifiedVaultConfig> config;
VaultConfigCache(VaultSettings settings) {
public VaultConfigCache(VaultSettings settings) {
this.settings = settings;
this.config = new AtomicReference<>(null);
}
@@ -9,6 +9,7 @@
package org.cryptomator.common.vaults;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.recovery.BackupRestorer;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -34,9 +35,7 @@ import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.Value.*;
@Singleton
public class VaultListManager {
@@ -67,6 +66,12 @@ public class VaultListManager {
autoLocker.init();
}
public boolean isAlreadyAdded(Path vaultPath) {
assert vaultPath.isAbsolute();
assert vaultPath.normalize().equals(vaultPath);
return vaultList.stream().anyMatch(v -> vaultPath.equals(v.getPath()));
}
public Vault add(Path pathToVault) throws IOException {
Path normalizedPathToVault = pathToVault.normalize().toAbsolutePath();
if (CryptoFileSystemProvider.checkDirStructureForVault(normalizedPathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) == DirStructure.UNRELATED) {
@@ -114,59 +119,122 @@ public class VaultListManager {
.findAny();
}
public void addVault(Vault vault) {
Path path = vault.getPath().normalize().toAbsolutePath();
if (!isAlreadyAdded(path)) {
vaultList.add(vault);
}
}
private Vault create(VaultSettings vaultSettings) {
var wrapper = new VaultConfigCache(vaultSettings);
try {
var vaultState = determineVaultState(vaultSettings.path.get());
if (vaultState == LOCKED) { //for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
wrapper.reloadConfig();
if (Objects.isNull(vaultSettings.lastKnownKeyLoader.get())) {
var keyIdScheme = wrapper.get().getKeyId().getScheme();
vaultSettings.lastKnownKeyLoader.set(keyIdScheme);
}
} else if (vaultState == NEEDS_MIGRATION) {
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
}
initializeLastKnownKeyLoaderIfPossible(vaultSettings, vaultState, wrapper);
return vaultComponentFactory.create(vaultSettings, wrapper, vaultState, null).vault();
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vaultSettings.path.get(), e);
LOG.warn("Failed to determine vault state for {}", vaultSettings.path.get(), e);
return vaultComponentFactory.create(vaultSettings, wrapper, ERROR, e).vault();
}
}
private void initializeLastKnownKeyLoaderIfPossible(VaultSettings vaultSettings, VaultState.Value vaultState, VaultConfigCache wrapper) throws IOException {
if (vaultSettings.lastKnownKeyLoader.get() != null) {
return;
}
switch (vaultState) {
case LOCKED -> {
wrapper.reloadConfig();
vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
}
case NEEDS_MIGRATION -> {
//for legacy reasons: pre v8 vault do not have a config, but they are in the NEEDS_MIGRATION state
vaultSettings.lastKnownKeyLoader.set(MasterkeyFileLoadingStrategy.SCHEME);
}
case VAULT_CONFIG_MISSING -> {
//Nothing to do here, since there is no config to read
}
case MISSING, ALL_MISSING, ERROR, PROCESSING -> {
// no config available or not safe to load
}
default -> {
if (Files.exists(vaultSettings.path.get().resolve(VAULTCONFIG_FILENAME))) {
try {
wrapper.reloadConfig();
vaultSettings.lastKnownKeyLoader.set(wrapper.get().getKeyId().getScheme());
} catch (IOException e) {
LOG.debug("Unable to load config for {}", vaultSettings.path.get(), e);
}
}
}
}
}
public static VaultState.Value redetermineVaultState(Vault vault) {
VaultState state = vault.stateProperty();
VaultState.Value previousState = state.getValue();
return switch (previousState) {
case LOCKED, NEEDS_MIGRATION, MISSING -> {
try {
var determinedState = determineVaultState(vault.getPath());
if (determinedState == LOCKED) {
vault.getVaultConfigCache().reloadConfig();
}
state.set(determinedState);
yield determinedState;
} catch (IOException e) {
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
state.set(ERROR);
vault.setLastKnownException(e);
yield ERROR;
}
VaultState.Value previous = state.getValue();
if (previous.equals(UNLOCKED) || previous.equals(PROCESSING)) {
return previous;
}
try {
VaultState.Value determined = determineVaultState(vault.getPath());
if (determined == LOCKED) {
vault.getVaultConfigCache().reloadConfig();
}
case ERROR, UNLOCKED, PROCESSING -> previousState;
};
state.set(determined);
return determined;
} catch (IOException e) {
LOG.warn("Failed to (re)determine vault state for {}", vault.getPath(), e);
vault.setLastKnownException(e);
state.set(ERROR);
return ERROR;
}
}
private static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
public static VaultState.Value determineVaultState(Path pathToVault) throws IOException {
if (!Files.exists(pathToVault)) {
return VaultState.Value.MISSING;
return MISSING;
}
VaultState.Value structureResult = checkDirStructure(pathToVault);
if (structureResult == LOCKED || structureResult == NEEDS_MIGRATION) {
return structureResult;
}
Path pathToVaultConfig = pathToVault.resolve(VAULTCONFIG_FILENAME);
Path pathToMasterkey = pathToVault.resolve(MASTERKEY_FILENAME);
if (!Files.exists(pathToVaultConfig)) {
BackupRestorer.restoreIfBackupPresent(pathToVault, VAULTCONFIG_FILENAME);
}
if (!Files.exists(pathToMasterkey)) {
BackupRestorer.restoreIfBackupPresent(pathToVault, MASTERKEY_FILENAME);
}
boolean hasConfig = Files.exists(pathToVaultConfig);
if (!hasConfig && !Files.exists(pathToMasterkey)) {
return ALL_MISSING;
}
if (!hasConfig) {
return VAULT_CONFIG_MISSING;
}
return checkDirStructure(pathToVault);
}
private static VaultState.Value checkDirStructure(Path pathToVault) throws IOException {
return switch (CryptoFileSystemProvider.checkDirStructureForVault(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME)) {
case VAULT -> VaultState.Value.LOCKED;
case UNRELATED -> VaultState.Value.MISSING;
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? //
VaultState.Value.NEEDS_MIGRATION //
: VaultState.Value.MISSING;
case VAULT -> LOCKED;
case UNRELATED -> MISSING;
case MAYBE_LEGACY -> Migrators.get().needsMigration(pathToVault, VAULTCONFIG_FILENAME, MASTERKEY_FILENAME) ? NEEDS_MIGRATION : MISSING;
};
}
@@ -25,6 +25,16 @@ public class VaultState extends ObservableValueBase<VaultState.Value> implements
*/
MISSING,
/**
* No vault config found at the provided path
*/
VAULT_CONFIG_MISSING,
/**
* No vault config and masterkey found at the provided path
*/
ALL_MISSING,
/**
* Vault requires migration to a newer vault format
*/
@@ -20,7 +20,6 @@ import java.util.ResourceBundle;
public class PasswordStrengthUtil {
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
private static final List<String> SANITIZED_INPUTS = List.of("cryptomator");
private final ResourceBundle resourceBundle;
@@ -48,13 +47,15 @@ public class PasswordStrengthUtil {
}
public String getStrengthDescription(Number score) {
if (score.intValue() == -1) {
return String.format(resourceBundle.getString(RESSOURCE_PREFIX + "tooShort"), minPwLength);
} else if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
return resourceBundle.getString(RESSOURCE_PREFIX + score.intValue());
} else {
return "";
}
return switch (score.intValue()) {
case -1 -> String.format(resourceBundle.getString("passwordStrength.messageLabel.tooShort"), minPwLength);
case 0 -> resourceBundle.getString("passwordStrength.messageLabel.0");
case 1 -> resourceBundle.getString("passwordStrength.messageLabel.1");
case 2 -> resourceBundle.getString("passwordStrength.messageLabel.2");
case 3 -> resourceBundle.getString("passwordStrength.messageLabel.3");
case 4 -> resourceBundle.getString("passwordStrength.messageLabel.4");
default -> "";
};
}
}
@@ -42,9 +42,10 @@ public enum FxmlFile {
QUIT("/fxml/quit.fxml"), //
QUIT_FORCED("/fxml/quit_forced.fxml"), //
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
RECOVERYKEY_EXPERT_SETTINGS("/fxml/recoverykey_expert_settings.fxml"), //
RECOVERYKEY_ONBOARDING("/fxml/recoverykey_onboarding.fxml"), //
RECOVERYKEY_RECOVER("/fxml/recoverykey_recover.fxml"), //
RECOVERYKEY_RESET_PASSWORD("/fxml/recoverykey_reset_password.fxml"), //
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
SHARE_VAULT("/fxml/share_vault.fxml"), //
SIMPLE_DIALOG("/fxml/simple_dialog.fxml"), //
@@ -4,8 +4,10 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.changepassword.PasswordStrengthUtil;
import org.cryptomator.ui.common.DefaultSceneFactory;
@@ -20,6 +22,7 @@ import org.cryptomator.ui.recoverykey.RecoveryKeyValidateController;
import javax.inject.Named;
import javax.inject.Provider;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
@@ -119,8 +122,8 @@ abstract class ConvertVaultModule {
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyValidateController.class)
static FxController bindRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
static FxController provideRecoveryKeyValidateController(@ConvertVaultWindow Vault vault, @ConvertVaultWindow VaultConfig.UnverifiedVaultConfig vaultConfig, @ConvertVaultWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, MasterkeyFileAccess masterkeyFileAccess) {
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, new SimpleObjectProperty<>(RecoveryActionType.CONVERT_VAULT), new SimpleObjectProperty<>(null));
}
}
@@ -194,7 +194,7 @@ public class DecryptFileNamesViewController implements FxController {
}
}
//obvservable getter
//observable getter
public ObservableValue<String> dropZoneTextProperty() {
return dropZoneText;
@@ -2,6 +2,7 @@ package org.cryptomator.ui.dialogs;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.controls.FontAwesome5Icon;
import org.cryptomator.ui.fxapp.FxApplicationScoped;
@@ -19,17 +20,21 @@ public class Dialogs {
private final ResourceBundle resourceBundle;
private final StageFactory stageFactory;
private final DefaultSceneFactory sceneFactory;
private static final String BUTTON_KEY_CLOSE = "generic.button.close";
@Inject
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) {
public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory, DefaultSceneFactory sceneFactory) {
this.resourceBundle = resourceBundle;
this.stageFactory = stageFactory;
this.sceneFactory = sceneFactory;
}
private static final Logger LOG = LoggerFactory.getLogger(Dialogs.class);
private SimpleDialog.Builder createDialogBuilder() {
return new SimpleDialog.Builder(resourceBundle, stageFactory);
return new SimpleDialog.Builder(resourceBundle, stageFactory, sceneFactory);
}
public SimpleDialog.Builder prepareRemoveVaultDialog(Stage window, Vault vault, ObservableList<Vault> vaults) {
@@ -47,6 +52,43 @@ public class Dialogs {
});
}
public SimpleDialog.Builder prepareContactHubVaultOwner(Stage window) {
return createDialogBuilder().setOwner(window) //
.setTitleKey("contactHubVaultOwner.title") //
.setMessageKey("contactHubVaultOwner.message") //
.setDescriptionKey("contactHubVaultOwner.description") //
.setIcon(FontAwesome5Icon.EXCLAMATION)//
.setOkButtonKey(BUTTON_KEY_CLOSE);
}
public SimpleDialog.Builder prepareRecoveryVaultAdded(Stage window, String displayName) {
return createDialogBuilder().setOwner(window) //
.setTitleKey("recover.existing.title") //
.setMessageKey("recover.existing.message") //
.setDescriptionKey("recover.existing.description", displayName) //
.setIcon(FontAwesome5Icon.CHECK)//
.setOkButtonKey(BUTTON_KEY_CLOSE);
}
public SimpleDialog.Builder prepareRecoveryVaultAlreadyExists(Stage window, String displayName) {
return createDialogBuilder().setOwner(window) //
.setTitleKey("recover.alreadyExists.title") //
.setMessageKey("recover.alreadyExists.message") //
.setDescriptionKey("recover.alreadyExists.description", displayName) //
.setIcon(FontAwesome5Icon.EXCLAMATION)//
.setOkButtonKey(BUTTON_KEY_CLOSE);
}
public SimpleDialog.Builder prepareRecoverPasswordSuccess(Stage window) {
return createDialogBuilder()
.setOwner(window) //
.setTitleKey("recoveryKey.recover.title") //
.setMessageKey("recoveryKey.recover.resetSuccess.message") //
.setDescriptionKey("recoveryKey.recover.resetSuccess.description") //
.setIcon(FontAwesome5Icon.CHECK)
.setOkAction(Stage::close)
.setOkButtonKey(BUTTON_KEY_CLOSE);
}
public SimpleDialog.Builder prepareRemoveCertDialog(Stage window, Settings settings) {
return createDialogBuilder() //
.setOwner(window) //
@@ -69,7 +111,7 @@ public class Dialogs {
.setMessageKey("dokanySupportEnd.message") //
.setDescriptionKey("dokanySupportEnd.description") //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
.setOkButtonKey("generic.button.close") //
.setOkButtonKey(BUTTON_KEY_CLOSE) //
.setCancelButtonKey("dokanySupportEnd.preferencesBtn") //
.setOkAction(Stage::close) //
.setCancelAction(cancelAction);
@@ -83,8 +125,20 @@ public class Dialogs {
.setDescriptionKey("retryIfReadonly.description") //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
.setOkButtonKey("retryIfReadonly.retry") //
.setCancelButtonKey("generic.button.close") //
.setCancelButtonKey(BUTTON_KEY_CLOSE) //
.setOkAction(okAction) //
.setCancelAction(Stage::close);
}
public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) {
return createDialogBuilder() //
.setOwner(window) //
.setTitleKey("recover.invalidSelection.title") //
.setMessageKey("recover.invalidSelection.message") //
.setDescriptionKey("recover.invalidSelection.description") //
.setIcon(FontAwesome5Icon.EXCLAMATION) //
.setOkButtonKey("generic.button.change") //
.setOkAction(Stage::close);
}
}
@@ -1,5 +1,6 @@
package org.cryptomator.ui.dialogs;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.StageFactory;
@@ -30,15 +31,15 @@ public class SimpleDialog {
FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
new SimpleDialogController(resolveText(builder.messageKey, null), //
resolveText(builder.descriptionKey, null), //
resolveText(builder.descriptionKey, builder.descriptionArgs), //
builder.icon, //
resolveText(builder.okButtonKey, null), //
builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
() -> builder.okAction.accept(dialogStage), //
() -> builder.cancelAction.accept(dialogStage)), //
Scene::new, builder.resourceBundle);
builder.sceneFactory, builder.resourceBundle);
dialogStage.setScene(new Scene(loaderFactory.load(FxmlFile.SIMPLE_DIALOG.getRessourcePathString()).getRoot()));
dialogStage.setScene(loaderFactory.createScene(FxmlFile.SIMPLE_DIALOG));
}
public void showAndWait() {
@@ -62,19 +63,22 @@ public class SimpleDialog {
private Stage owner;
private final ResourceBundle resourceBundle;
private final StageFactory stageFactory;
private final DefaultSceneFactory sceneFactory;
private String titleKey;
private String[] titleArgs;
private String messageKey;
private String descriptionKey;
private String[] descriptionArgs;
private String okButtonKey;
private String cancelButtonKey;
private FontAwesome5Icon icon;
private Consumer<Stage> okAction = Stage::close;
private Consumer<Stage> cancelAction = Stage::close;
public Builder(ResourceBundle resourceBundle, StageFactory stageFactory) {
public Builder(ResourceBundle resourceBundle, StageFactory stageFactory, DefaultSceneFactory sceneFactory) {
this.resourceBundle = resourceBundle;
this.stageFactory = stageFactory;
this.sceneFactory = sceneFactory;
}
public Builder setOwner(Stage owner) {
@@ -93,8 +97,9 @@ public class SimpleDialog {
return this;
}
public Builder setDescriptionKey(String descriptionKey) {
public Builder setDescriptionKey(String descriptionKey, String... args) {
this.descriptionKey = descriptionKey;
this.descriptionArgs = args;
return this;
}
@@ -15,15 +15,13 @@ import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
import javax.inject.Named;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
@@ -40,7 +38,8 @@ import java.io.InputStream;
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
ShareVaultComponent.class, //
EventViewComponent.class})
EventViewComponent.class, //
RecoveryKeyComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {
@@ -28,7 +28,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.*;
@FxApplicationScoped
public class FxApplicationTerminator {
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR);
private static final Set<VaultState.Value> STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR, VAULT_CONFIG_MISSING, ALL_MISSING);
private static final Set<VaultState.Value> STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING);
private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class);
@@ -1,14 +1,18 @@
package org.cryptomator.ui.keyloading.masterkeyfile;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.KeyLoading;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
@@ -27,17 +31,41 @@ public class ChooseMasterkeyFileController implements FxController {
private final Stage window;
private final Vault vault;
private final CompletableFuture<Path> result;
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final ResourceBundle resourceBundle;
@FXML
private CheckBox restoreInsteadCheckBox;
@FXML
private Button forwardButton;
@Inject
public ChooseMasterkeyFileController(@KeyLoading Stage window, @KeyLoading Vault vault, CompletableFuture<Path> result, ResourceBundle resourceBundle) {
public ChooseMasterkeyFileController(@KeyLoading Stage window, //
@KeyLoading Vault vault, //
CompletableFuture<Path> result, //
RecoveryKeyComponent.Factory recoveryKeyWindow, //
ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
this.result = result;
this.recoveryKeyWindow = recoveryKeyWindow;
this.resourceBundle = resourceBundle;
this.window.setOnHiding(this::windowClosed);
}
@FXML
private void initialize() {
restoreInsteadCheckBox.selectedProperty().addListener((_, _, newVal) -> {
if (newVal) {
forwardButton.setText(resourceBundle.getString("addvaultwizard.existing.restore"));
forwardButton.setOnAction(_ -> restoreMasterkey());
} else {
forwardButton.setText(resourceBundle.getString("generic.button.choose"));
forwardButton.setOnAction(_ -> proceed());
}
});
}
@FXML
public void cancel() {
window.close();
@@ -47,6 +75,13 @@ public class ChooseMasterkeyFileController implements FxController {
result.cancel(true);
}
@FXML
void restoreMasterkey() {
Stage ownerStage = (Stage) window.getOwner();
window.close();
recoveryKeyWindow.create(vault, ownerStage, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_MASTERKEY)).showOnboardingDialogWindow();
}
@FXML
public void proceed() {
LOG.trace("proceed()");
@@ -62,7 +97,7 @@ public class ChooseMasterkeyFileController implements FxController {
//--- Setter & Getter ---
public String getDisplayName(){
public String getDisplayName() {
return vault.getDisplayName();
}
@@ -18,6 +18,7 @@ import org.cryptomator.ui.error.ErrorComponent;
import org.cryptomator.ui.fxapp.FxApplicationTerminator;
import org.cryptomator.ui.fxapp.PrimaryStage;
import org.cryptomator.ui.migration.MigrationComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.cryptomator.ui.stats.VaultStatisticsComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
@@ -32,7 +33,7 @@ import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class})
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, VaultStatisticsComponent.class, WrongFileAlertComponent.class, ErrorComponent.class, RecoveryKeyComponent.class})
abstract class MainWindowModule {
@Provides
@@ -52,7 +52,7 @@ public class VaultDetailController implements FxController {
case LOCKED -> FontAwesome5Icon.LOCK;
case PROCESSING -> FontAwesome5Icon.SPINNER;
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
};
} else {
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
@@ -1,20 +1,26 @@
package org.cryptomator.ui.mainwindow;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import javax.inject.Inject;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import java.io.File;
import java.nio.file.Files;
import java.util.ResourceBundle;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_GLOB;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
@MainWindowScoped
public class VaultDetailMissingVaultController implements FxController {
@@ -23,6 +29,7 @@ public class VaultDetailMissingVaultController implements FxController {
private final ObservableList<Vault> vaults;
private final ResourceBundle resourceBundle;
private final Stage window;
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final Dialogs dialogs;
@Inject
@@ -30,11 +37,13 @@ public class VaultDetailMissingVaultController implements FxController {
ObservableList<Vault> vaults, //
ResourceBundle resourceBundle, //
@MainWindow Stage window, //
Dialogs dialogs) {
Dialogs dialogs, //
RecoveryKeyComponent.Factory recoveryKeyWindow) {
this.vault = vault;
this.vaults = vaults;
this.resourceBundle = resourceBundle;
this.window = window;
this.recoveryKeyWindow = recoveryKeyWindow;
this.dialogs = dialogs;
}
@@ -48,6 +57,19 @@ public class VaultDetailMissingVaultController implements FxController {
dialogs.prepareRemoveVaultDialog(window, vault.get(), vaults).build().showAndWait();
}
@FXML
void restoreVaultConfig() {
if(KeyLoadingStrategy.isHubVault(vault.get().getVaultSettings().lastKnownKeyLoader.get())){
dialogs.prepareContactHubVaultOwner(window).build().showAndWait();
}
else if(Files.exists(vault.get().getPath().resolve(MASTERKEY_FILENAME))){
recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
}
else {
recoveryKeyWindow.create(vault.get(), window, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
}
}
@FXML
void changeLocation() {
// copied from ChooseExistingVaultController class
@@ -55,7 +55,7 @@ public class VaultListCellController implements FxController {
case LOCKED -> FontAwesome5Icon.LOCK;
case PROCESSING -> FontAwesome5Icon.SPINNER;
case UNLOCKED -> FontAwesome5Icon.LOCK_OPEN;
case NEEDS_MIGRATION, MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
case NEEDS_MIGRATION, MISSING, VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> FontAwesome5Icon.EXCLAMATION_TRIANGLE;
};
} else {
return FontAwesome5Icon.EXCLAMATION_TRIANGLE;
@@ -20,11 +20,13 @@ import javafx.stage.Stage;
import java.util.EnumSet;
import java.util.Objects;
import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.Value.UNLOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
@MainWindowScoped
public class VaultListContextMenuController implements FxController {
@@ -63,7 +65,7 @@ public class VaultListContextMenuController implements FxController {
this.selectedVaultState = selectedVault.flatMap(Vault::stateProperty).orElse(null);
this.selectedVaultPassphraseStored = selectedVault.map(this::isPasswordStored).orElse(false);
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION)::contains).orElse(false);
this.selectedVaultRemovable = selectedVaultState.map(EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING)::contains).orElse(false);
this.selectedVaultUnlockable = selectedVaultState.map(LOCKED::equals).orElse(false);
this.selectedVaultLockable = selectedVaultState.map(UNLOCKED::equals).orElse(false);
}
@@ -1,11 +1,16 @@
package org.cryptomator.ui.mainwindow;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.recovery.VaultPreparator;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultComponent;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.integrations.mount.MountService;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
@@ -13,6 +18,7 @@ import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxFSEventList;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,6 +28,7 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
@@ -37,11 +44,14 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.stage.DirectoryChooser;
import javafx.stage.Stage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
@@ -50,10 +60,12 @@ import java.util.stream.Collectors;
import static org.cryptomator.common.Constants.CRYPTOMATOR_FILENAME_EXT;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
import static org.cryptomator.common.vaults.VaultState.Value.ALL_MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.ERROR;
import static org.cryptomator.common.vaults.VaultState.Value.LOCKED;
import static org.cryptomator.common.vaults.VaultState.Value.MISSING;
import static org.cryptomator.common.vaults.VaultState.Value.NEEDS_MIGRATION;
import static org.cryptomator.common.vaults.VaultState.Value.VAULT_CONFIG_MISSING;
@MainWindowScoped
public class VaultListController implements FxController {
@@ -75,6 +87,10 @@ public class VaultListController implements FxController {
private final ObservableValue<Double> cellSize;
private final Dialogs dialogs;
private final VaultComponent.Factory vaultComponentFactory;
private final RecoveryKeyComponent.Factory recoveryKeyWindow;
private final List<MountService> mountServices;
public ListView<Vault> vaultList;
public StackPane root;
@FXML
@@ -94,6 +110,9 @@ public class VaultListController implements FxController {
FxApplicationWindows appWindows, //
Settings settings, //
Dialogs dialogs, //
RecoveryKeyComponent.Factory recoveryKeyWindow, //
VaultComponent.Factory vaultComponentFactory, //
List<MountService> mountServices, //
FxFSEventList fxFSEventList) {
this.mainWindow = mainWindow;
this.vaults = vaults;
@@ -105,6 +124,9 @@ public class VaultListController implements FxController {
this.resourceBundle = resourceBundle;
this.appWindows = appWindows;
this.dialogs = dialogs;
this.recoveryKeyWindow = recoveryKeyWindow;
this.vaultComponentFactory = vaultComponentFactory;
this.mountServices = mountServices;
this.emptyVaultList = Bindings.isEmpty(vaults);
this.unreadEvents = fxFSEventList.unreadEventsProperty();
@@ -204,6 +226,26 @@ public class VaultListController implements FxController {
VaultListManager.redetermineVaultState(newValue);
}
private Optional<Path> chooseValidVaultDirectory() {
DirectoryChooser directoryChooser = new DirectoryChooser();
File selectedDirectory;
do {
selectedDirectory = directoryChooser.showDialog(mainWindow);
if (selectedDirectory == null) {
return Optional.empty();
}
Path selectedPath = selectedDirectory.toPath();
if (!Files.isDirectory(selectedPath.resolve(Constants.DATA_DIR_NAME))) {
dialogs.prepareNoDDirectorySelectedDialog(mainWindow).build().showAndWait();
selectedDirectory = null;
}
} while (selectedDirectory == null);
return Optional.of(selectedDirectory.toPath());
}
@FXML
public void didClickAddNewVault() {
addVaultWizard.build().showAddNewVaultWizard(resourceBundle);
@@ -214,9 +256,40 @@ public class VaultListController implements FxController {
addVaultWizard.build().showAddExistingVaultWizard(resourceBundle);
}
@FXML
public void didClickRecoverExistingVault() {
Optional<Path> selectedDirectory = chooseValidVaultDirectory();
if (selectedDirectory.isEmpty()) {
return;
}
Path path = selectedDirectory.get();
Optional<Vault> matchingVaultListEntry = vaultListManager.get(path);
if (matchingVaultListEntry.isPresent()) {
dialogs.prepareRecoveryVaultAlreadyExists(mainWindow, matchingVaultListEntry.get().getDisplayName()) //
.setOkAction(Stage::close) //
.build().showAndWait();
return;
}
Vault preparedVault = VaultPreparator.prepareVault(path, vaultComponentFactory, mountServices, resourceBundle);
VaultListManager.redetermineVaultState(preparedVault);
switch (preparedVault.getState()) {
case VAULT_CONFIG_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_VAULT_CONFIG)).showOnboardingDialogWindow();
case ALL_MISSING -> recoveryKeyWindow.create(preparedVault, mainWindow, new SimpleObjectProperty<>(RecoveryActionType.RESTORE_ALL)).showOnboardingDialogWindow();
case LOCKED, NEEDS_MIGRATION -> {
vaultListManager.addVault(preparedVault);
dialogs.prepareRecoveryVaultAdded(mainWindow, preparedVault.getDisplayName()).setOkAction(Stage::close).build().showAndWait();
}
default -> LOG.warn("Unhandled vault state during recovery: {}", preparedVault.getState());
}
}
private void pressedShortcutToRemoveVault() {
final var vault = selectedVault.get();
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION).contains(vault.getState())) {
if (vault != null && EnumSet.of(LOCKED, MISSING, ERROR, NEEDS_MIGRATION, ALL_MISSING, VAULT_CONFIG_MISSING).contains(vault.getState())) {
dialogs.prepareRemoveVaultDialog(mainWindow, vault, vaults).build().showAndWait();
}
}
@@ -3,11 +3,13 @@ package org.cryptomator.ui.recoverykey;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Named;
import javafx.beans.property.ObjectProperty;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -24,6 +26,9 @@ public interface RecoveryKeyComponent {
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER)
Lazy<Scene> recoverScene();
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
Lazy<Scene> recoverOnboardingScene();
default void showRecoveryKeyCreationWindow() {
Stage stage = window();
stage.setScene(creationScene().get());
@@ -38,11 +43,19 @@ public interface RecoveryKeyComponent {
stage.show();
}
default void showOnboardingDialogWindow() {
Stage stage = window();
stage.setScene(recoverOnboardingScene().get());
stage.sizeToScene();
stage.show();
}
@Subcomponent.Factory
interface Factory {
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, @BindsInstance @Named("keyRecoveryOwner") Stage owner);
RecoveryKeyComponent create(@BindsInstance @RecoveryKeyWindow Vault vault, //
@BindsInstance @Named("keyRecoveryOwner") Stage owner, //
@BindsInstance @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType);
}
}
@@ -1,28 +1,45 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.recovery.CryptoFsInitializer;
import org.cryptomator.common.recovery.MasterkeyService;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.recovery.RecoveryDirectory;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.Animations;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.controls.FormattedLabel;
import org.cryptomator.ui.controls.NiceSecurePasswordField;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
@RecoveryKeyScoped
public class RecoveryKeyCreationController implements FxController {
@@ -30,23 +47,71 @@ public class RecoveryKeyCreationController implements FxController {
private final Stage window;
private final Lazy<Scene> successScene;
private final Lazy<Scene> recoverykeyExpertSettingsScene;
private final MasterkeyFileAccess masterkeyFileAccess;
private final Vault vault;
private final ExecutorService executor;
private final RecoveryKeyFactory recoveryKeyFactory;
private final StringProperty recoveryKeyProperty;
private final FxApplicationWindows appWindows;
public NiceSecurePasswordField passwordField;
private final IntegerProperty shorteningThreshold;
private final ObjectProperty<RecoveryActionType> recoverType;
private final ResourceBundle resourceBundle;
public FormattedLabel descriptionLabel;
public Button cancelButton;
public Button nextButton;
private final VaultListManager vaultListManager;
private final Dialogs dialogs;
@Inject
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, FxApplicationWindows appWindows, ResourceBundle resourceBundle) {
public RecoveryKeyCreationController(FxApplicationWindows appWindows, //
@RecoveryKeyWindow Stage window, //
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
@RecoveryKeyWindow Vault vault, //
RecoveryKeyFactory recoveryKeyFactory, //
MasterkeyFileAccess masterkeyFileAccess, //
ExecutorService executor, //
@RecoveryKeyWindow StringProperty recoveryKey, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
VaultListManager vaultListManager, //
ResourceBundle resourceBundle, //
Dialogs dialogs) {
this.window = window;
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
this.successScene = successScene;
this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
this.vault = vault;
this.executor = executor;
this.recoveryKeyFactory = recoveryKeyFactory;
this.recoveryKeyProperty = recoveryKey;
this.appWindows = appWindows;
this.recoverType = recoverType;
this.resourceBundle = resourceBundle;
this.masterkeyFileAccess = masterkeyFileAccess;
this.shorteningThreshold = shorteningThreshold;
this.vaultListManager = vaultListManager;
this.dialogs = dialogs;
}
@FXML
public void initialize() {
if (recoverType.get() == RecoveryActionType.SHOW_KEY) {
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
} else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
cancelButton.setOnAction((_) -> back());
cancelButton.setText(resourceBundle.getString("generic.button.back"));
nextButton.setOnAction((_) -> restoreWithPassword());
}
}
@FXML
public void back() {
window.setScene(recoverykeyExpertSettingsScene.get());
window.centerOnScreen();
}
@FXML
@@ -71,6 +136,42 @@ public class RecoveryKeyCreationController implements FxController {
executor.submit(task);
}
@FXML
public void restoreWithPassword() {
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
Path recoveryPath = recoveryDirectory.getRecoveryPath();
Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
var combo = MasterkeyService.detect(masterkey, vault.getPath())
.orElseThrow(() -> new IllegalStateException("Could not detect combo for vault path: " + vault.getPath()));
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), combo);
}
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
vaultListManager.add(vault.getPath());
}
window.close();
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
.setTitleKey("recover.recoverVaultConfig.title") //
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
.build().showAndWait();
} catch (InvalidPassphraseException e) {
LOG.info("Password invalid", e);
Animations.createShakeWindowAnimation(window).play();
} catch (IOException | CryptoException | IllegalStateException e) {
LOG.error("Recovery process failed", e);
appWindows.showErrorWindow(e, window, null);
}
}
@FXML
public void close() {
window.close();
@@ -0,0 +1,123 @@
package org.cryptomator.ui.recoverykey;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.stage.Stage;
import dagger.Lazy;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.controls.NumericTextField;
@RecoveryKeyScoped
public class RecoveryKeyExpertSettingsController implements FxController {
public static final int MAX_SHORTENING_THRESHOLD = 220;
public static final int MIN_SHORTENING_THRESHOLD = 36;
private static final String DOCS_NAME_SHORTENING_URL = "https://docs.cryptomator.org/security/vault/#name-shortening";
private final Stage window;
private final Lazy<Application> application;
private final Vault vault;
private final ObjectProperty<RecoveryActionType> recoverType;
private final IntegerProperty shorteningThreshold;
private final Lazy<Scene> resetPasswordScene;
private final Lazy<Scene> createScene;
private final Lazy<Scene> onBoardingScene;
private final Lazy<Scene> recoverScene;
private final BooleanBinding validShorteningThreshold;
@FXML
public CheckBox expertSettingsCheckBox;
@FXML
public NumericTextField shorteningThresholdTextField;
@Inject
public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, //
Lazy<Application> application, //
@RecoveryKeyWindow Vault vault, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy<Scene> createScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverScene) {
this.window = window;
this.application = application;
this.vault = vault;
this.recoverType = recoverType;
this.shorteningThreshold = shorteningThreshold;
this.resetPasswordScene = resetPasswordScene;
this.createScene = createScene;
this.onBoardingScene = onBoardingScene;
this.recoverScene = recoverScene;
this.validShorteningThreshold = Bindings.createBooleanBinding(this::isValidShorteningThreshold, shorteningThreshold);
}
@FXML
public void initialize() {
shorteningThresholdTextField.setPromptText(MIN_SHORTENING_THRESHOLD + "-" + MAX_SHORTENING_THRESHOLD);
shorteningThresholdTextField.setText(Integer.toString(MAX_SHORTENING_THRESHOLD));
shorteningThresholdTextField.textProperty().addListener((_, _, newValue) -> {
try {
int intValue = Integer.parseInt(newValue);
shorteningThreshold.set(intValue);
} catch (NumberFormatException e) {
shorteningThreshold.set(0); //the value is set to 0 to ensure that an invalid value assignment is detected during a NumberFormatException
}
});
}
@FXML
public void toggleUseExpertSettings() {
if (!expertSettingsCheckBox.isSelected()) {
shorteningThresholdTextField.setText(Integer.toString(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD));
}
}
public void openDocs() {
application.get().getHostServices().showDocument(DOCS_NAME_SHORTENING_URL);
}
public BooleanBinding validShorteningThresholdProperty() {
return validShorteningThreshold;
}
public boolean isValidShorteningThreshold() {
var value = shorteningThreshold.get();
return value >= MIN_SHORTENING_THRESHOLD && value <= MAX_SHORTENING_THRESHOLD;
}
@FXML
public void back() {
if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.VAULT_CONFIG_MISSING) {
window.setScene(recoverScene.get());
} else if (recoverType.get() == RecoveryActionType.RESTORE_ALL && vault.getState() == VaultState.Value.ALL_MISSING) {
window.setScene(recoverScene.get());
} else if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
window.setScene(onBoardingScene.get());
}
}
@FXML
public void next() {
if (recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG) {
window.setScene(createScene.get());
} else {
window.setScene(resetPasswordScene.get());
}
}
}
@@ -5,8 +5,12 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.addvaultwizard.CreateNewVaultExpertSettingsController;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
@@ -19,6 +23,10 @@ import org.cryptomator.ui.common.StageFactory;
import javax.inject.Named;
import javax.inject.Provider;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
@@ -99,12 +107,18 @@ abstract class RecoveryKeyModule {
}
@Provides
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS)
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING)
@RecoveryKeyScoped
static Scene provideRecoveryKeyResetPasswordSuccessScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS);
static Scene provideRecoveryKeyOnboardingScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_ONBOARDING);
}
@Provides
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS)
@RecoveryKeyScoped
static Scene provideRecoveryKeyExpertSettingsScene(@RecoveryKeyWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS);
}
// ------------------
@@ -120,6 +134,25 @@ abstract class RecoveryKeyModule {
return new RecoveryKeyDisplayController(window, vault.getDisplayName(), recoveryKey.get(), localization);
}
@Provides
@Named("shorteningThreshold")
@RecoveryKeyScoped
static IntegerProperty provideShorteningThreshold() {
return new SimpleIntegerProperty(CreateNewVaultExpertSettingsController.MAX_SHORTENING_THRESHOLD);
}
@Provides
@Named("cipherCombo")
@RecoveryKeyScoped
static ObjectProperty<CryptorProvider.Scheme> provideCipherCombo() {
return new SimpleObjectProperty<>();
}
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyExpertSettingsController.class)
abstract FxController provideRecoveryKeyExpertSettingsController(RecoveryKeyExpertSettingsController controller);
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyRecoverController.class)
@@ -137,14 +170,14 @@ abstract class RecoveryKeyModule {
@Binds
@IntoMap
@FxControllerKey(RecoveryKeyResetPasswordSuccessController.class)
abstract FxController bindRecoveryKeyResetPasswordSuccessController(RecoveryKeyResetPasswordSuccessController controller);
@FxControllerKey(RecoveryKeyOnboardingController.class)
abstract FxController bindRecoveryKeyOnboardingController(RecoveryKeyOnboardingController controller);
@Provides
@IntoMap
@FxControllerKey(RecoveryKeyValidateController.class)
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory);
static FxController bindRecoveryKeyValidateController(@RecoveryKeyWindow Vault vault, @RecoveryKeyWindow @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, @RecoveryKeyWindow StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory, @Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, @Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) {
return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, recoverType, cipherCombo);
}
@Provides
@@ -0,0 +1,176 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.ResourceBundle;
import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_ALL;
import static org.cryptomator.common.recovery.RecoveryActionType.RESTORE_VAULT_CONFIG;
@RecoveryKeyScoped
public class RecoveryKeyOnboardingController implements FxController {
private final Stage window;
private final Vault vault;
private final Lazy<Scene> recoverykeyRecoverScene;
private final Lazy<Scene> recoverykeyExpertSettingsScene;
private final ObjectProperty<RecoveryActionType> recoverType;
private final ResourceBundle resourceBundle;
public Label titleLabel;
public Label messageLabel;
public Label pleaseConfirm;
public Label secondTextDesc;
@FXML
private CheckBox affirmationBox;
@FXML
private RadioButton recoveryKeyRadio;
@FXML
private RadioButton passwordRadio;
@FXML
private Button nextButton;
@FXML
private VBox chooseMethodeBox;
@FXML
private ToggleGroup methodToggleGroup = new ToggleGroup();
@FXML
private HBox hBox;
@Inject
public RecoveryKeyOnboardingController(@RecoveryKeyWindow Stage window, //
@RecoveryKeyWindow Vault vault, //
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverykeyExpertSettingsScene, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
ResourceBundle resourceBundle) {
this.window = window;
this.vault = vault;
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
this.recoverykeyExpertSettingsScene = recoverykeyExpertSettingsScene;
this.recoverType = recoverType;
this.resourceBundle = resourceBundle;
}
@FXML
public void initialize() {
recoveryKeyRadio.setToggleGroup(methodToggleGroup);
passwordRadio.setToggleGroup(methodToggleGroup);
BooleanBinding showMethodSelection = Bindings.createBooleanBinding(
() -> recoverType.get() == RecoveryActionType.RESTORE_VAULT_CONFIG, recoverType);
chooseMethodeBox.visibleProperty().bind(showMethodSelection);
chooseMethodeBox.managedProperty().bind(showMethodSelection);
nextButton.disableProperty().bind(
affirmationBox.selectedProperty().not()
.or(methodToggleGroup.selectedToggleProperty().isNull().and(showMethodSelection))
);
switch (recoverType.get()) {
case RESTORE_MASTERKEY -> {
window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
messageLabel.setVisible(false);
messageLabel.setManaged(false);
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
}
case RESTORE_ALL -> {
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
messageLabel.setVisible(true);
messageLabel.setManaged(true);
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.otherwisePleaseConfirm"));
}
case RESTORE_VAULT_CONFIG -> {
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
messageLabel.setVisible(false);
messageLabel.setManaged(false);
pleaseConfirm.setText(resourceBundle.getString("recover.onBoarding.pleaseConfirm"));
}
default -> window.setTitle("");
}
if (vault.getState() == VaultState.Value.ALL_MISSING) {
messageLabel.setText(resourceBundle.getString("recover.onBoarding.allMissing.intro"));
} else {
messageLabel.setText(resourceBundle.getString("recover.onBoarding.intro"));
}
titleLabel.textProperty().bind(Bindings.createStringBinding(() ->
recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY
? resourceBundle.getString("recover.recoverMasterkey.title")
: resourceBundle.getString("recover.recoverVaultConfig.title"), recoverType));
BooleanBinding isRestoreMasterkey = Bindings.createBooleanBinding(
() -> recoverType.get() == RecoveryActionType.RESTORE_MASTERKEY, recoverType);
hBox.minHeightProperty().bind(Bindings.when(isRestoreMasterkey).then(206.0).otherwise(Region.USE_COMPUTED_SIZE));
secondTextDesc.textProperty().bind(Bindings.createStringBinding(() -> {
RecoveryActionType type = recoverType.get();
Toggle sel = methodToggleGroup.getSelectedToggle();
return switch (type) {
case RESTORE_VAULT_CONFIG -> resourceBundle.getString(sel == passwordRadio
? "recover.onBoarding.intro.password"
: "recover.onBoarding.intro.recoveryKey");
case RESTORE_MASTERKEY -> resourceBundle.getString("recover.onBoarding.intro.masterkey.recoveryKey");
case RESTORE_ALL -> resourceBundle.getString("recover.onBoarding.intro.recoveryKey");
default -> "";
};
}, recoverType, methodToggleGroup.selectedToggleProperty()));
showMethodSelection.addListener((_, _, nowShown) -> {
if (nowShown && methodToggleGroup.getSelectedToggle() == null) {
methodToggleGroup.selectToggle(recoveryKeyRadio);
}
});
}
@FXML
public void close() {
window.close();
}
@FXML
public void next() {
switch (recoverType.get()) {
case RESTORE_VAULT_CONFIG, RESTORE_ALL -> {
Object selectedToggle = methodToggleGroup.getSelectedToggle();
if (selectedToggle == recoveryKeyRadio) {
recoverType.set(RESTORE_ALL);
window.setScene(recoverykeyRecoverScene.get());
} else if (selectedToggle == passwordRadio) {
recoverType.set(RESTORE_VAULT_CONFIG);
window.setScene(recoverykeyExpertSettingsScene.get());
} else {
window.setScene(recoverykeyRecoverScene.get());
}
}
case RESTORE_MASTERKEY -> window.setScene(recoverykeyRecoverScene.get());
default -> window.setScene(recoverykeyRecoverScene.get()); // Fallback
}
window.centerOnScreen();
}
}
@@ -1,54 +1,105 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.common.vaults.VaultState;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.Observable;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javax.inject.Named;
import javafx.beans.property.ObjectProperty;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.util.ResourceBundle;
@RecoveryKeyScoped
public class RecoveryKeyRecoverController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
private final Stage window;
private final Lazy<Scene> resetPasswordScene;
private final Vault vault;
private final Lazy<Scene> nextScene;
private final Lazy<Scene> onBoardingScene;
private final ResourceBundle resourceBundle;
public ObjectProperty<RecoveryActionType> recoverType;
@FXML
private Button cancelButton;
@FXML
RecoveryKeyValidateController recoveryKeyValidateController;
@Inject
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, ResourceBundle resourceBundle) {
public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, //
@RecoveryKeyWindow Vault vault, //
ResourceBundle resourceBundle, //
@FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy<Scene> resetPasswordScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> expertSettingsScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy<Scene> onBoardingScene, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType) {
this.window = window;
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
this.resetPasswordScene = resetPasswordScene;
this.vault = vault;
this.resourceBundle = resourceBundle;
this.onBoardingScene = onBoardingScene;
this.recoverType = recoverType;
this.nextScene = switch (recoverType.get()) {
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
window.setTitle(resourceBundle.getString("recover.recoverVaultConfig.title"));
yield expertSettingsScene;
}
case RESTORE_MASTERKEY -> {
window.setTitle(resourceBundle.getString("recover.recoverMasterkey.title"));
yield resetPasswordScene;
}
case RESET_PASSWORD -> {
window.setTitle(resourceBundle.getString("recoveryKey.recover.title"));
yield resetPasswordScene;
}
case SHOW_KEY -> {
window.setTitle(resourceBundle.getString("recoveryKey.display.title"));
yield resetPasswordScene;
}
default -> throw new IllegalArgumentException("Unexpected recovery action type: " + recoverType.get());
};
}
@FXML
public void initialize() {
if (recoverType.get() == RecoveryActionType.RESET_PASSWORD) {
cancelButton.setText(resourceBundle.getString("generic.button.cancel"));
} else {
cancelButton.setText(resourceBundle.getString("generic.button.back"));
}
}
@FXML
public void close() {
window.close();
public void closeOrReturn() {
switch (recoverType.get()) {
case RESET_PASSWORD -> window.close();
case RESTORE_MASTERKEY -> {
window.setScene(onBoardingScene.get());
window.centerOnScreen();
}
default -> {
if(vault.getState().equals(VaultState.Value.ALL_MISSING)){
recoverType.set(RecoveryActionType.RESTORE_ALL);
}
else {
recoverType.set(RecoveryActionType.RESTORE_VAULT_CONFIG);
}
window.setScene(onBoardingScene.get());
window.centerOnScreen();
}
}
}
@FXML
public void recover() {
window.setScene(resetPasswordScene.get());
window.setScene(nextScene.get());
}
/* Getter/Setter */
@@ -1,25 +1,44 @@
package org.cryptomator.ui.recoverykey;
import dagger.Lazy;
import org.cryptomator.common.recovery.CryptoFsInitializer;
import org.cryptomator.common.recovery.MasterkeyService;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.recovery.RecoveryDirectory;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.changepassword.NewPasswordController;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
import static org.cryptomator.common.Constants.VAULTCONFIG_FILENAME;
@RecoveryKeyScoped
public class RecoveryKeyResetPasswordController implements FxController {
@@ -30,48 +49,140 @@ public class RecoveryKeyResetPasswordController implements FxController {
private final RecoveryKeyFactory recoveryKeyFactory;
private final ExecutorService executor;
private final StringProperty recoveryKey;
private final Lazy<Scene> recoverResetPasswordSuccessScene;
private final Lazy<Scene> recoverExpertSettingsScene;
private final Lazy<Scene> recoverykeyRecoverScene;
private final FxApplicationWindows appWindows;
private final MasterkeyFileAccess masterkeyFileAccess;
private final VaultListManager vaultListManager;
private final IntegerProperty shorteningThreshold;
private final ObjectProperty<RecoveryActionType> recoverType;
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
private final ResourceBundle resourceBundle;
private final Dialogs dialogs;
public NewPasswordController newPasswordController;
public Button nextButton;
@Inject
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey, @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD_SUCCESS) Lazy<Scene> recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) {
public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, //
@RecoveryKeyWindow Vault vault, //
RecoveryKeyFactory recoveryKeyFactory, //
ExecutorService executor, //
@RecoveryKeyWindow StringProperty recoveryKey, //
@FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy<Scene> recoverExpertSettingsScene, //
@FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy<Scene> recoverykeyRecoverScene, //
FxApplicationWindows appWindows, //
MasterkeyFileAccess masterkeyFileAccess, //
VaultListManager vaultListManager, //
@Named("shorteningThreshold") IntegerProperty shorteningThreshold, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo, //
ResourceBundle resourceBundle, //
Dialogs dialogs) {
this.window = window;
this.vault = vault;
this.recoveryKeyFactory = recoveryKeyFactory;
this.executor = executor;
this.recoveryKey = recoveryKey;
this.recoverResetPasswordSuccessScene = recoverResetPasswordSuccessScene;
this.recoverExpertSettingsScene = recoverExpertSettingsScene;
this.recoverykeyRecoverScene = recoverykeyRecoverScene;
this.appWindows = appWindows;
this.masterkeyFileAccess = masterkeyFileAccess;
this.vaultListManager = vaultListManager;
this.shorteningThreshold = shorteningThreshold;
this.cipherCombo = cipherCombo;
this.recoverType = recoverType;
this.resourceBundle = resourceBundle;
this.dialogs = dialogs;
}
@FXML
public void initialize() {
switch (recoverType.get()) {
case RESTORE_MASTERKEY, RESTORE_ALL -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn"));
case RESET_PASSWORD -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.resetBtn"));
default -> nextButton.setText(resourceBundle.getString("recoveryKey.recover.recoverBtn")); // Fallback
}
}
@FXML
public void close() {
window.close();
switch (recoverType.get()) {
case RESTORE_ALL -> window.setScene(recoverExpertSettingsScene.get());
case RESTORE_MASTERKEY, RESET_PASSWORD -> window.setScene(recoverykeyRecoverScene.get());
default -> window.close();
}
}
@FXML
public void next() {
switch (recoverType.get()) {
case RESTORE_ALL -> restorePassword();
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
default -> resetPassword(); // Fallback
}
}
@FXML
public void restorePassword() {
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
Path recoveryPath = recoveryDirectory.getRecoveryPath();
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, recoveryPath.resolve(MASTERKEY_FILENAME), newPasswordController.passwordField.getCharacters())) {
CryptoFsInitializer.init(recoveryPath, masterkey, shorteningThreshold.get(), cipherCombo.get());
}
recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
vaultListManager.add(vault.getPath());
}
window.close();
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
.setTitleKey("recover.recoverVaultConfig.title") //
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
.build().showAndWait();
} catch (IOException | CryptoException e) {
LOG.error("Recovery process failed", e);
appWindows.showErrorWindow(e, window, null);
}
}
@FXML
public void resetPassword() {
Task<Void> task = new ResetPasswordTask();
task.setOnScheduled(event -> {
task.setOnScheduled(_ -> {
LOG.debug("Using recovery key to reset password for {}.", vault.getDisplayablePath());
});
task.setOnSucceeded(event -> {
LOG.info("Used recovery key to reset password for {}.", vault.getDisplayablePath());
window.setScene(recoverResetPasswordSuccessScene.get());
task.setOnSucceeded(_ -> {
LOG.debug("Used recovery key to reset password for {}.", vault.getDisplayablePath());
window.close();
switch (recoverType.get()){
case RESET_PASSWORD -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).build().showAndWait();
case RESTORE_MASTERKEY -> dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()).setTitleKey("recover.recoverMasterkey.title").setMessageKey("recoveryKey.recover.resetMasterkeyFileSuccess.message").build().showAndWait();
default -> dialogs.prepareRecoverPasswordSuccess(window).build().showAndWait(); // Fallback
}
});
task.setOnFailed(event -> {
task.setOnFailed(_ -> {
LOG.error("Resetting password failed.", task.getException());
appWindows.showErrorWindow(task.getException(), window, null);
});
executor.submit(task);
}
private class ResetPasswordTask extends Task<Void> {
private ResetPasswordTask() {
setOnFailed(event -> LOG.error("Failed to reset password", getException()));
private static final Logger LOG = LoggerFactory.getLogger(ResetPasswordTask.class);
public ResetPasswordTask() {
setOnFailed(_ -> LOG.error("Failed to reset password", getException()));
}
@Override
@@ -79,7 +190,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
recoveryKeyFactory.newMasterkeyFileWithPassphrase(vault.getPath(), recoveryKey.get(), newPasswordController.passwordField.getCharacters());
return null;
}
}
/* Getter/Setter */
@@ -1,24 +0,0 @@
package org.cryptomator.ui.recoverykey;
import org.cryptomator.ui.common.FxController;
import javax.inject.Inject;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@RecoveryKeyScoped
public class RecoveryKeyResetPasswordSuccessController implements FxController {
private final Stage window;
@Inject
public RecoveryKeyResetPasswordSuccessController(@RecoveryKeyWindow Stage window) {
this.window = window;
}
@FXML
public void close() {
window.close();
}
}
@@ -1,18 +1,23 @@
package org.cryptomator.ui.recoverykey;
import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.ObservableUtil;
import org.cryptomator.common.recovery.MasterkeyService;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptofs.VaultConfigLoadException;
import org.cryptomator.cryptofs.VaultKeyInvalidException;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
import org.cryptomator.ui.common.FxController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty;
@@ -22,10 +27,12 @@ import javafx.scene.control.TextArea;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import java.io.IOException;
import java.util.NoSuchElementException;
public class RecoveryKeyValidateController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class);
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyValidateController.class);
private static final CharMatcher ALLOWED_CHARS = CharMatcher.inRange('a', 'z').or(CharMatcher.is(' '));
private final Vault vault;
@@ -36,13 +43,23 @@ public class RecoveryKeyValidateController implements FxController {
private final ObservableValue<Boolean> recoveryKeyInvalid;
private final RecoveryKeyFactory recoveryKeyFactory;
private final ObjectProperty<RecoveryKeyState> recoveryKeyState;
private final ObjectProperty<CryptorProvider.Scheme> cipherCombo;
private final AutoCompleter autoCompleter;
private final ObjectProperty<RecoveryActionType> recoverType;
private final MasterkeyFileAccess masterkeyFileAccess;
private volatile boolean isWrongKey;
public TextArea textarea;
public RecoveryKeyValidateController(Vault vault, @Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, StringProperty recoveryKey, RecoveryKeyFactory recoveryKeyFactory) {
public RecoveryKeyValidateController(Vault vault, //
@Nullable VaultConfig.UnverifiedVaultConfig vaultConfig, //
StringProperty recoveryKey, //
RecoveryKeyFactory recoveryKeyFactory, //
MasterkeyFileAccess masterkeyFileAccess, //
@Named("recoverType") ObjectProperty<RecoveryActionType> recoverType, //
@Named("cipherCombo") ObjectProperty<CryptorProvider.Scheme> cipherCombo
) {
this.vault = vault;
this.unverifiedVaultConfig = vaultConfig;
this.recoveryKey = recoveryKey;
@@ -52,6 +69,9 @@ public class RecoveryKeyValidateController implements FxController {
this.recoveryKeyCorrect = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.CORRECT::equals, false);
this.recoveryKeyWrong = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.WRONG::equals, false);
this.recoveryKeyInvalid = ObservableUtil.mapWithDefault(recoveryKeyState, RecoveryKeyState.INVALID::equals, false);
this.recoverType = recoverType;
this.cipherCombo = cipherCombo;
this.masterkeyFileAccess = masterkeyFileAccess;
}
@FXML
@@ -117,14 +137,37 @@ public class RecoveryKeyValidateController implements FxController {
}
private void validateRecoveryKey() {
isWrongKey = false;
var valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
if (valid) {
recoveryKeyState.set(RecoveryKeyState.CORRECT);
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
recoveryKeyState.set(RecoveryKeyState.WRONG);
} else {
recoveryKeyState.set(RecoveryKeyState.INVALID);
switch (recoverType.get()) {
case RESTORE_ALL, RESTORE_VAULT_CONFIG -> {
try {
var scheme = MasterkeyService.validateRecoveryKeyAndDetectCombo(recoveryKeyFactory, vault, recoveryKey.get(), masterkeyFileAccess);
cipherCombo.set(scheme);
recoveryKeyState.set(RecoveryKeyState.CORRECT);
} catch (CryptoException e) {
LOG.info("Recovery key is valid but crypto scheme couldn't be determined", e);
recoveryKeyState.set(RecoveryKeyState.WRONG);
} catch (IllegalArgumentException e) {
LOG.info("Recovery key is syntactically invalid", e);
recoveryKeyState.set(RecoveryKeyState.INVALID);
} catch (IOException e) {
LOG.warn("IO error while validating recovery key", e);
recoveryKeyState.set(RecoveryKeyState.INVALID);
} catch (NoSuchElementException e) {
LOG.warn("Could not determine scheme from masterkey during recovery key validation, because no valid *.c9r file is present in vault", e);
recoveryKeyState.set(RecoveryKeyState.INVALID);
}
}
case RESTORE_MASTERKEY, RESET_PASSWORD, SHOW_KEY, CONVERT_VAULT -> {
isWrongKey = false;
boolean valid = recoveryKeyFactory.validateRecoveryKey(recoveryKey.get(), unverifiedVaultConfig != null ? this::checkKeyAgainstVaultConfig : null);
if (valid) {
recoveryKeyState.set(RecoveryKeyState.CORRECT);
} else if (isWrongKey) { //set via side effect in checkKeyAgainstVaultConfig()
recoveryKeyState.set(RecoveryKeyState.WRONG);
} else {
recoveryKeyState.set(RecoveryKeyState.INVALID);
}
}
}
}
@@ -58,9 +58,15 @@ public class ShareVaultController implements FxController {
private static URI getHubUri(Vault vault) {
try {
var keyID = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
assert keyID.getScheme().startsWith(SCHEME_PREFIX);
return new URI(keyID.getScheme().substring(SCHEME_PREFIX.length()) + "://" + keyID.getHost() + "/app/vaults");
var keyId = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
assert keyId.getScheme().startsWith(SCHEME_PREFIX);
var path = keyId.getPath();
var apiIdx = path.indexOf("/api/");
if (apiIdx < 0) {
throw new IllegalArgumentException("Path does not contain /api/: " + path);
}
var appPath = path.substring(0, apiIdx) + "/app/vaults";
return new URI(keyId.getScheme().substring(SCHEME_PREFIX.length()), keyId.getAuthority(), appPath, null, null);
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (URISyntaxException e) {
@@ -15,6 +15,7 @@ import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.jetbrains.annotations.Nullable;
import javax.inject.Named;
@@ -27,7 +28,7 @@ import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module(subcomponents = {KeyLoadingComponent.class})
@Module(subcomponents = {KeyLoadingComponent.class, RecoveryKeyComponent.class})
abstract class UnlockModule {
@Provides
@@ -1,16 +1,16 @@
package org.cryptomator.ui.vaultoptions;
import org.cryptomator.common.keychain.KeychainManager;
import org.cryptomator.common.recovery.RecoveryActionType;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.changepassword.ChangePasswordComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.forgetpassword.ForgetPasswordComponent;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.stage.Stage;
@@ -18,8 +18,6 @@ import javafx.stage.Stage;
@VaultOptionsScoped
public class MasterkeyOptionsController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(MasterkeyOptionsController.class);
private final Vault vault;
private final Stage window;
private final ChangePasswordComponent.Builder changePasswordWindow;
@@ -51,12 +49,12 @@ public class MasterkeyOptionsController implements FxController {
@FXML
public void showRecoveryKey() {
recoveryKeyWindow.create(vault, window).showRecoveryKeyCreationWindow();
recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.SHOW_KEY)).showRecoveryKeyCreationWindow();
}
@FXML
public void showRecoverVaultDialog() {
recoveryKeyWindow.create(vault, window).showRecoveryKeyRecoverWindow();
recoveryKeyWindow.create(vault, window, new SimpleObjectProperty<>(RecoveryActionType.RESET_PASSWORD)).showRecoveryKeyRecoverWindow();
}
@FXML
@@ -6,7 +6,12 @@
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.Group?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordConvertController"
minWidth="400"
@@ -17,7 +22,16 @@
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="KEY" glyphSize="24"/>
</StackPane>
</Group>
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
<fx:include fx:id="newPassword" source="new_password.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
@@ -26,7 +40,7 @@
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert"> <!-- for button logic, see controller -->
<Button fx:id="convertBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#convert">
<graphic>
<FontAwesome5Spinner glyphSize="12"/>
</graphic>
@@ -34,5 +48,5 @@
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
</VBox>
</HBox>
@@ -5,7 +5,10 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml"
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.Group?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.convertvault.HubToPasswordStartController"
minWidth="400"
@@ -16,18 +19,26 @@
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="SYNC" glyphSize="24"/>
</StackPane>
</Group>
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#next" disable="${!controller.validateController.recoveryKeyCorrect}"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#next" disable="${!controller.validateController.recoveryKeyCorrect}"/>
</buttons>
</ButtonBar>
</VBox>
</HBox>
@@ -40,7 +40,7 @@
<Insets bottom="6" top="6"/>
</padding>
</Label>
<FormattedLabel format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true">
<FormattedLabel fx:id="descriptionLabel" format="%recoveryKey.create.description" arg1="${controller.vault.displayName}" wrapText="true">
<padding>
<Insets bottom="6"/>
</padding>
@@ -49,8 +49,8 @@
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#createRecoveryKey" disable="${passwordField.text.empty}"/>
<Button fx:id="cancelButton" text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#createRecoveryKey" disable="${passwordField.text.empty}"/>
</buttons>
</ButtonBar>
</VBox>
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Hyperlink?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NumericTextField?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyExpertSettingsController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="QUESTION" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="Expert Settings" wrapText="true">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<CheckBox fx:id="expertSettingsCheckBox" text="%addvaultwizard.new.expertSettings.enableExpertSettingsCheckbox" onAction="#toggleUseExpertSettings"/>
<VBox spacing="6" visible="${expertSettingsCheckBox.selected}">
<HBox spacing="2" HBox.hgrow="NEVER">
<Label text="%addvaultwizard.new.expertSettings.shorteningThreshold.title"/>
<Region prefWidth="2"/>
<Hyperlink contentDisplay="GRAPHIC_ONLY" onAction="#openDocs">
<graphic>
<FontAwesome5IconView glyph="QUESTION_CIRCLE" styleClass="glyph-icon-muted"/>
</graphic>
<tooltip>
<Tooltip text="%addvaultwizard.new.expertSettings.shorteningThreshold.tooltip" showDelay="10ms"/>
</tooltip>
</Hyperlink>
</HBox>
<Label text="%recover.expertSettings.shorteningThreshold.title" wrapText="true"/>
<NumericTextField fx:id="shorteningThresholdTextField"/>
<HBox alignment="TOP_RIGHT">
<Region minWidth="4" prefWidth="4" HBox.hgrow="NEVER"/>
<StackPane>
<Label styleClass="label-muted" text="%addvaultwizard.new.expertSettings.shorteningThreshold.invalid" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${!controller.validShorteningThreshold}" managed="${!controller.validShorteningThreshold}" graphicTextGap="6">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-red" glyph="TIMES"/>
</graphic>
</Label>
<Label styleClass="label-muted" text="%addvaultwizard.new.expertSettings.shorteningThreshold.valid" textAlignment="RIGHT" alignment="CENTER_RIGHT" visible="${controller.validShorteningThreshold}" managed="${controller.validShorteningThreshold}" graphicTextGap="6">
<graphic>
<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="CHECK"/>
</graphic>
</Label>
</StackPane>
</HBox>
</VBox>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#back"/>
<Button text="%generic.button.next" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" onAction="#next"/>
</buttons>
</ButtonBar>
</VBox>
</HBox>
@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.Group?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:id="hBox"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyOnboardingController"
prefWidth="480"
minHeight="242"
spacing="12"
VBox.vgrow="ALWAYS">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="SYNC" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS" spacing="12">
<Label fx:id="titleLabel" text="%recover.recoverVaultConfig.title" styleClass="label-extra-large"/>
<VBox spacing="6">
<VBox fx:id="chooseMethodeBox" spacing="6">
<Label text="%recover.onBoarding.chooseMethod"/>
<RadioButton fx:id="recoveryKeyRadio" text="%recover.onBoarding.useRecoveryKey" selected="true">
<toggleGroup>
<ToggleGroup fx:id="methodToggleGroup"/>
</toggleGroup>
</RadioButton>
<RadioButton fx:id="passwordRadio" text="%recover.onBoarding.usePassword"/>
</VBox>
<Label fx:id="messageLabel" wrapText="true"/>
<Label fx:id="pleaseConfirm" wrapText="true"/>
<GridPane alignment="CENTER_LEFT">
<padding>
<Insets left="6"/>
</padding>
<columnConstraints>
<ColumnConstraints minWidth="20" halignment="LEFT"/>
<ColumnConstraints />
</columnConstraints>
<rowConstraints>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
</rowConstraints>
<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<Label text="%recover.onBoarding.intro.ensure" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1"/>
<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<Label fx:id="secondTextDesc" text="%recover.onBoarding.intro.recoveryKey" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1"/>
</GridPane>
<CheckBox fx:id="affirmationBox" text="%recover.onBoarding.affirmation"/>
</VBox>
<Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX" VBox.vgrow="NEVER">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD"
disable="${!affirmationBox.selected}" defaultButton="true" onAction="#next"/>
</buttons>
</ButtonBar>
</VBox>
</HBox>
@@ -3,32 +3,46 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.Group?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyRecoverController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="SYNC" glyphSize="24"/>
</StackPane>
</Group>
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
<fx:include fx:id="recoveryKeyValidate" source="recoverykey_validate.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<ButtonBar buttonMinWidth="120" buttonOrder="+BX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="cancelButton" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#closeOrReturn"/>
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#recover" disable="${!controller.validateController.recoveryKeyCorrect}"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
</HBox>
@@ -5,7 +5,12 @@
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.Group?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyResetPasswordController"
minWidth="400"
@@ -17,17 +22,28 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include fx:id="newPassword" source="new_password.fxml"/>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="KEY" glyphSize="24"/>
</StackPane>
</Group>
<VBox spacing="12" HBox.hgrow="ALWAYS" alignment="TOP_CENTER">
<fx:include fx:id="newPassword" source="new_password.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button text="%recoveryKey.recover.resetBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#resetPassword" disable="${!controller.passwordSufficientAndMatching}"/>
</buttons>
</ButtonBar>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
<Button fx:id="nextButton" ButtonBar.buttonData="FINISH" defaultButton="true" disable="${!controller.passwordSufficientAndMatching}" onAction="#next"/>
</buttons>
</ButtonBar>
</VBox>
</VBox>
</children>
</VBox>
</HBox>
@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.Region?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyResetPasswordSuccessController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_LEFT">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Group>
<StackPane>
<padding>
<Insets topRightBottomLeft="6"/>
</padding>
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
</StackPane>
</Group>
<VBox HBox.hgrow="ALWAYS">
<Label styleClass="label-large" text="%recoveryKey.recover.resetSuccess.message" wrapText="true" textAlignment="LEFT">
<padding>
<Insets bottom="6" top="6"/>
</padding>
</Label>
<Label text="%recoveryKey.recover.resetSuccess.description" wrapText="true" textAlignment="LEFT"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.close" ButtonBar.buttonData="CANCEL_CLOSE" defaultButton="true" cancelButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</HBox>
@@ -2,48 +2,40 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyValidateController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
spacing="12">
<children>
<FormattedLabel format="%recoveryKey.recover.prompt" arg1="${controller.vault.displayName}" wrapText="true"/>
<TextArea wrapText="true" prefRowCount="4" fx:id="textarea" textFormatter="${controller.recoveryKeyTextFormatter}" onKeyPressed="#onKeyPressed"/>
<StackPane>
<Label text="Just some Filler" visible="false" graphicTextGap="6">
<VBox>
<Label text="Just some Filler" visible="false" managed="${textarea.text.empty}" graphicTextGap="6">
<graphic>
<FontAwesome5IconView glyph="ANCHOR"/>
</graphic>
</Label>
<Label text="%recoveryKey.recover.correctKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyCorrect}">
<Label text="%recoveryKey.recover.correctKey" graphicTextGap="6" visible="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyCorrect}" managed="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyCorrect}">
<graphic>
<FontAwesome5IconView glyph="CHECK"/>
</graphic>
</Label>
<Label text="%recoveryKey.recover.wrongKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyWrong}">
<Label text="%recoveryKey.recover.wrongKey" graphicTextGap="6" visible="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyWrong}" managed="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyWrong}">
<graphic>
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
</graphic>
</Label>
<Label text="%recoveryKey.recover.invalidKey" graphicTextGap="6" contentDisplay="LEFT" visible="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyInvalid}">
<Label text="%recoveryKey.recover.invalidKey" graphicTextGap="6" visible="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyInvalid}" managed="${(!textarea.text.empty) &amp;&amp; controller.recoveryKeyInvalid}">
<graphic>
<FontAwesome5IconView glyph="TIMES" styleClass="glyph-icon-red"/>
</graphic>
</Label>
</StackPane>
</VBox>
</children>
</VBox>
@@ -12,6 +12,7 @@
<?import javafx.scene.Group?>
<?import javafx.scene.layout.Region?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<?import javafx.scene.control.CheckBox?>
<HBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.keyloading.masterkeyfile.ChooseMasterkeyFileController"
@@ -40,12 +41,13 @@
</padding>
</Label>
<FormattedLabel format="%unlock.chooseMasterkey.description" arg1="${controller.displayName}" wrapText="true"/>
<CheckBox fx:id="restoreInsteadCheckBox" text="%unlock.chooseMasterkey.restoreInstead" wrapText="true"/>
<Region VBox.vgrow="ALWAYS" minHeight="18"/>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
<buttons>
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel"/>
<Button text="%generic.button.choose" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
<Button fx:id="forwardButton" text="%generic.button.choose" ButtonBar.buttonData="NEXT_FORWARD" defaultButton="true" onAction="#proceed"/>
</buttons>
</ButtonBar>
</VBox>
@@ -53,5 +53,6 @@
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing.fxml" visible="${controller.vault.missing}" managed="${controller.vault.missing}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_needsmigration.fxml" visible="${controller.vault.needsMigration}" managed="${controller.vault.needsMigration}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_unknownerror.fxml" visible="${controller.vault.unknownError}" managed="${controller.vault.unknownError}"/>
<fx:include VBox.vgrow="ALWAYS" source="vault_detail_missing_vault_config.fxml" visible="${controller.vault.missingVaultConfig}" managed="${controller.vault.missingVaultConfig}"/>
</children>
</VBox>
@@ -0,0 +1,42 @@
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailMissingVaultController"
alignment="TOP_CENTER"
spacing="9">
<VBox spacing="9" alignment="CENTER">
<StackPane>
<Circle styleClass="glyph-icon-primary" radius="48"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="FILE" glyphSize="48"/>
<FontAwesome5IconView styleClass="glyph-icon-primary" glyph="SEARCH" glyphSize="24">
<StackPane.margin>
<Insets top="12"/>
</StackPane.margin>
</FontAwesome5IconView>
</StackPane>
<Label text="%main.vaultDetail.missingVaultConfig.info" wrapText="true"/>
</VBox>
<VBox spacing="6" alignment="CENTER">
<Button text="%main.vaultDetail.missing.recheck" minWidth="120" onAction="#recheck">
<graphic>
<FontAwesome5IconView glyph="REDO"/>
</graphic>
</Button>
<Button text="%main.vaultDetail.missingVaultConfig.restore" minWidth="120" onAction="#restoreVaultConfig">
<graphic>
<FontAwesome5IconView glyph="MAGIC"/>
</graphic>
</Button>
<Button text="%main.vaultDetail.missing.remove" minWidth="120" onAction="#didClickRemoveVault">
<graphic>
<FontAwesome5IconView glyph="TRASH"/>
</graphic>
</Button>
</VBox>
</VBox>
+5 -2
View File
@@ -13,8 +13,6 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Arc?>
<?import javafx.scene.shape.Circle?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.shape.Rectangle?>
<?import javafx.scene.layout.AnchorPane?>
<StackPane xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
@@ -80,6 +78,11 @@
<FontAwesome5IconView glyph="FOLDER_OPEN" textAlignment="CENTER" wrappingWidth="14"/>
</graphic>
</MenuItem>
<MenuItem styleClass="dropdown-button-context-menu-item" text="%main.vaultlist.addVaultBtn.menuItemRecover" onAction="#didClickRecoverExistingVault">
<graphic>
<FontAwesome5IconView glyph="SYNC" textAlignment="CENTER" wrappingWidth="14"/>
</graphic>
</MenuItem>
</items>
</ContextMenu>
</fx:define>
+59 -2
View File
@@ -97,6 +97,7 @@ addvault.new.readme.accessLocation.4=Feel free to remove this file.
## Existing
addvaultwizard.existing.title=Add Existing Vault
addvaultwizard.existing.instruction=Choose the "vault.cryptomator" file of your existing vault. If only a file named "masterkey.cryptomator" exists, select that instead.
addvaultwizard.existing.restore=Restore…
addvaultwizard.existing.chooseBtn=Choose…
addvaultwizard.existing.filePickerTitle=Select Vault File
addvaultwizard.existing.filePickerMimeDesc=Cryptomator Vault
@@ -128,6 +129,7 @@ unlock.unlockBtn=Unlock
## Select
unlock.chooseMasterkey.message=Masterkey file not found
unlock.chooseMasterkey.description=Cryptomator could not find the masterkey file for vault "%s". Please choose the key file manually.
unlock.chooseMasterkey.restoreInstead=Restore the masterkey file instead
unlock.chooseMasterkey.filePickerTitle=Select Masterkey File
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator Masterkey
## Success
@@ -395,8 +397,9 @@ main.vaultlist.contextMenu.unlockNow=Unlock Now
main.vaultlist.contextMenu.vaultoptions=Show Vault Options
main.vaultlist.contextMenu.reveal=Reveal Drive
main.vaultlist.contextMenu.share=Share…
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault...
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault...
main.vaultlist.addVaultBtn.menuItemNew=Create New Vault
main.vaultlist.addVaultBtn.menuItemExisting=Open Existing Vault
main.vaultlist.addVaultBtn.menuItemRecover=Recover Existing Vault…
main.vaultlist.showEventsButton.tooltip=Open event view
##Notificaition
main.notification.updateAvailable=Update is available.
@@ -434,6 +437,9 @@ main.vaultDetail.missing.info=Cryptomator could not find a vault at this path.
main.vaultDetail.missing.recheck=Recheck
main.vaultDetail.missing.remove=Remove from Vault List…
main.vaultDetail.missing.changeLocation=Change Vault Location…
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=Vault config is missing.
main.vaultDetail.missingVaultConfig.restore=Restore vault config
### Needs Migration
main.vaultDetail.migrateButton=Upgrade Vault
main.vaultDetail.migratePrompt=Your vault needs to be upgraded to a new format, before you can access it
@@ -498,6 +504,7 @@ vaultOptions.hub.convertBtn=Convert to Password-Based Vault
recoveryKey.display.title=Show Recovery Key
recoveryKey.create.message=Password required
recoveryKey.create.description=Enter the password for "%s" to show its recovery key.
recoveryKey.recover.description=Enter the password for "%s" to recover the vault config.
recoveryKey.display.description=The following recovery key can be used to restore access to "%s":
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
## Reset Password
@@ -510,9 +517,59 @@ recoveryKey.recover.invalidKey=This recovery key is not valid
recoveryKey.printout.heading=Cryptomator Recovery Key\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Reset
recoveryKey.recover.recoverBtn=Recover
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Password reset successful
recoveryKey.recover.resetSuccess.description=You can unlock your vault with the new password.
### Recovery Key Vault Config Reset Success
recoveryKey.recover.resetVaultConfigSuccess.message=Vault configuration recovered
recoveryKey.recover.resetMasterkeyFileSuccess.message=Masterkey file recovered
recoveryKey.recover.resetMasterkeyFileSuccess.description=You can unlock your vault with your password now.
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
recover.existing.title=Vault Added
recover.existing.message=The vault was added successfully
recover.existing.description=Your vault "%s" has been added to the vault list. No recovery process was necessary.
##Vault Already Exists - Dialog
recover.alreadyExists.title=Vault Already Exists
recover.alreadyExists.message=This vault has already been added
recover.alreadyExists.description=Your vault "%s" is already present in your vault list and was therefore not added again.
##Invalid Selection - Dialog
recover.invalidSelection.title=Invalid Selection
recover.invalidSelection.message=Your selection is not a vault
recover.invalidSelection.description=The selected folder must be a valid Cryptomator vault.
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub Vault
contactHubVaultOwner.message=This vault was created with Cryptomator Hub
contactHubVaultOwner.description=Please reach out to the vault owner to restore the missing file. They can download the vault template from Cryptomator Hub.
##Dialog Title
recover.recoverVaultConfig.title=Recover Vault Config
recover.recoverMasterkey.title=Recover Masterkey
## OnBoarding
recover.onBoarding.chooseMethod=Choose recovery method:
recover.onBoarding.useRecoveryKey=Use Recovery Key
recover.onBoarding.usePassword=Use Password
recover.onBoarding.intro=Make sure to check the following:
recover.onBoarding.pleaseConfirm=Before proceeding, please confirm that:
recover.onBoarding.otherwisePleaseConfirm=Otherwise, please confirm that:
recover.onBoarding.allMissing.intro=If this vault is managed by Cryptomator Hub, the vault owner must restore it for you.
recover.onBoarding.intro.ensure=All files are fully synced.
recover.onBoarding.affirmation=I have read and understood these requirements
###Vault Config Missing
recover.onBoarding.intro.recoveryKey=You have the recovery key and know if expert settings were used.
recover.onBoarding.intro.password=You have the vault password and know if expert settings were used.
###Masterkey Missing
recover.onBoarding.intro.masterkey.recoveryKey=You have the vault recovery key.
## Expert Settings
recover.expertSettings.shorteningThreshold.title=This value must match the one used before recovery to ensure compatibility with previously encrypted data.
# Convert Vault
convertVault.title=Convert Vault
@@ -87,6 +87,7 @@
### Locked
### Unlocked
### Missing
### Missing Vault Config
### Needs Migration
### Error
@@ -105,6 +106,25 @@
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
+21 -2
View File
@@ -394,8 +394,6 @@ main.vaultlist.contextMenu.unlockNow=افتح الان
main.vaultlist.contextMenu.vaultoptions=إظهار خيارات المخزن
main.vaultlist.contextMenu.reveal=اظهار القرص
main.vaultlist.contextMenu.share=مشاركة…
main.vaultlist.addVaultBtn.menuItemNew=إنشاء مخزن جديد...
main.vaultlist.addVaultBtn.menuItemExisting=افتح مخزن موجود...
main.vaultlist.showEventsButton.tooltip=عرض الإشعارات
##Notificaition
main.notification.updateAvailable=هناك تحديث متاح.
@@ -433,6 +431,7 @@ main.vaultDetail.missing.info=لم يتمكن Cryptomator من العثور عل
main.vaultDetail.missing.recheck=إعادة الفحص
main.vaultDetail.missing.remove=حذف من قائمة الخزنات…
main.vaultDetail.missing.changeLocation=تغيير موقع الخزنة…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=ترقية الحافظة
main.vaultDetail.migratePrompt=يجب ترقية المخزن الخاص بك إلى تنسيق جديد، قبل أن تتمكن من الوصول إليه
@@ -512,6 +511,26 @@ recoveryKey.recover.resetBtn=إعادة الضبط
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=تم إعادة تعيين كلمة المرور بنجاح
recoveryKey.recover.resetSuccess.description=يمكنك فتح الخزانة الخاصة بك بكلمة المرور الجديدة.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=خزانة Hub
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=تحويل الخزانة
@@ -394,6 +394,7 @@ main.vaultDetail.missing.info=Cryptomator был юлдан һаҡлағыс т
main.vaultDetail.missing.recheck=Яңынан тикшер
main.vaultDetail.missing.remove=Һаҡлағыс исемлегенән алып ташла…
main.vaultDetail.missing.changeLocation=Һаҡлағыс урынын үҙгәрт…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Һаҡлағысты яңыртыу
main.vaultDetail.migratePrompt=Һаҡлағысҡа инер алдынан уны яңы форматҡа тиклем яңыртыу кәрәк
@@ -472,6 +473,26 @@ recoveryKey.recover.resetBtn=Яңынан башла
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Серһүҙ яңыртыу уңышлы тамамланды
recoveryKey.recover.resetSuccess.description=Яңы серһүҙ менән һаҡлағысты аса алаһығыҙ.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Һаҡлағыс хабы
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Һаҡлағысты үҙгәртеү
@@ -381,6 +381,7 @@ main.vaultDetail.missing.info=Cryptomator ня змог знайсці скар
main.vaultDetail.missing.recheck=Пераправерыць
main.vaultDetail.missing.remove=Выдаліць са спісу скарбніц…
main.vaultDetail.missing.changeLocation=Змяніць месцазнаходжанне скарбніцы…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Абнавіць скарбніцу
main.vaultDetail.migratePrompt=Тваю скарбніцу трэба сканвертаваць у новы фармат, перад тым як ты зможаш атрымаць доступ да яе
@@ -454,6 +455,25 @@ recoveryKey.recover.resetBtn=Скінуць
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Пароль паспяхова скінуты
recoveryKey.recover.resetSuccess.description=Ты можаш разамкнуць сваю скарбніцу з дапамогаю новага паролю.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Перабудаваць скарбніцу
@@ -395,6 +395,7 @@ main.vaultDetail.missing.info=Криптоматор не намира хран
main.vaultDetail.missing.recheck=Повторен опит
main.vaultDetail.missing.remove=Премахване от списъка с хранилищата…
main.vaultDetail.missing.changeLocation=Смяна на мястото на хранилището…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Надграждане на хранилище
main.vaultDetail.migratePrompt=Преди да можете да го достъпвате, хранилището трябва да бъде надградено до новия формат
@@ -473,6 +474,26 @@ recoveryKey.recover.resetBtn=Нулиране
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Паролата е променена
recoveryKey.recover.resetSuccess.description=Можете да отключите хранилището с новата парола.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Хранилище на Hub
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Преобразуване на хранилище
@@ -149,6 +149,7 @@ main.vaultlist.contextMenu.lock=লক করুন
### Unlocked
main.vaultDetail.lockBtn=লক করুন
### Missing
### Missing Vault Config
### Needs Migration
### Error
@@ -169,6 +170,25 @@ vaultOptions.mount.mountPoint.directoryPickerButton=নির্বাচন ক
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
@@ -241,6 +241,7 @@ main.vaultDetail.missing.info=Cryptomator nije mogao pronaći disk na ovoj lokac
main.vaultDetail.missing.recheck=Provjeri ponovo
main.vaultDetail.missing.remove=Ukloni sa liste sefova…
main.vaultDetail.missing.changeLocation=Promijeni lokaciju sefa…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Nadogradi sef
main.vaultDetail.migratePrompt=Da biste mogli pristupiti svom sefu, morate ga nadograditi na novi format
@@ -294,6 +295,25 @@ recoveryKey.recover.correctKey=Ključ za oporavak je ispravan
recoveryKey.printout.heading=Cryptomator Ključ za oporavak za \n"%s"\n
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
+21 -2
View File
@@ -394,8 +394,6 @@ main.vaultlist.contextMenu.unlockNow=Desbloqueja ara
main.vaultlist.contextMenu.vaultoptions=Opcions de la caixa forta
main.vaultlist.contextMenu.reveal=Mostra la unitat
main.vaultlist.contextMenu.share=Compateix…
main.vaultlist.addVaultBtn.menuItemNew=Crea una nova caixa forta…
main.vaultlist.addVaultBtn.menuItemExisting=Obri una caixa forta existent...
##Notificaition
main.notification.updateAvailable=Hi ha una actualització disponible.
main.notification.support=Doneu suport a Cryptomator.
@@ -429,6 +427,7 @@ main.vaultDetail.missing.info=Cryptomator no ha trobat una caixa forta en aquest
main.vaultDetail.missing.recheck=Torna a comprovar
main.vaultDetail.missing.remove=Eliminar de la llista de la caixa forta…
main.vaultDetail.missing.changeLocation=Canvia la localització de la caixa forta…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Actualitza la caixa forta
main.vaultDetail.migratePrompt=Per accedir a la vostra caixa forta abans cal actualitzar-la al nou format
@@ -508,6 +507,26 @@ recoveryKey.recover.resetBtn=Reinicia
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=S'ha modificat la contrasenya correctament
recoveryKey.recover.resetSuccess.description=Pots desbloquejar la caixa forta amb la nova contrasenya.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Caixa forta de Hub
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Convertir la caixa forta
@@ -396,6 +396,7 @@ main.vaultDetail.missing.info=Cryptomator nemohl najít trezor na této cestě.
main.vaultDetail.missing.recheck=Znovu zkontrolovat
main.vaultDetail.missing.remove=Odebrat ze seznamu trezorů…
main.vaultDetail.missing.changeLocation=Změnit umístění trezoru…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Upgrade trezoru
main.vaultDetail.migratePrompt=Váš trezor musí být aktualizován na nový formát, než k němu budete mít přístup
@@ -473,6 +474,26 @@ recoveryKey.recover.resetBtn=Resetovat
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Resetování hesla bylo úspěšné
recoveryKey.recover.resetSuccess.description=Můžete odemknout váš trezor pomocí nového hesla.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub trezor
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Převést trezor
+87 -2
View File
@@ -96,6 +96,7 @@ addvault.new.readme.accessLocation.4=Fjern denne fil hvis du har lyst.
## Existing
addvaultwizard.existing.title=Tilføj Eksisterende Boks
addvaultwizard.existing.instruction=Vælgt filen "vault.cryptomator" i mappen med dine boks-filer. Hvis der kun findes en fil med navnet "masterkey.cryptomator", skal du vælge den i stedet.
addvaultwizard.existing.restore=Gendan…
addvaultwizard.existing.chooseBtn=Vælg…
addvaultwizard.existing.filePickerTitle=Vælg boks-fil
addvaultwizard.existing.filePickerMimeDesc=Cryptomator boks
@@ -127,6 +128,7 @@ unlock.unlockBtn=Lås op
## Select
unlock.chooseMasterkey.message=Hovednøgle-fil ikke fundet
unlock.chooseMasterkey.description=Cryptomator kunne ikke finde hovednøgle-filen for boksen "%s". Vælg venligst filen manuelt.
unlock.chooseMasterkey.restoreInstead=Gendan i stedet masterkey-filen
unlock.chooseMasterkey.filePickerTitle=Vælg hovednøgle-fil
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator hovednøgle
## Success
@@ -394,8 +396,9 @@ main.vaultlist.contextMenu.unlockNow=Lås op nu
main.vaultlist.contextMenu.vaultoptions=Vis boksindstillinger
main.vaultlist.contextMenu.reveal=Vis drev
main.vaultlist.contextMenu.share=Del…
main.vaultlist.addVaultBtn.menuItemNew=Opret ny boks...
main.vaultlist.addVaultBtn.menuItemExisting=Åbn eksisterende boks...
main.vaultlist.addVaultBtn.menuItemNew=Opret ny boks
main.vaultlist.addVaultBtn.menuItemExisting=Åbn eksisterende boks
main.vaultlist.addVaultBtn.menuItemRecover=Genopret eksisterende boks…
main.vaultlist.showEventsButton.tooltip=Åbn begivenhedsvisning
##Notificaition
main.notification.updateAvailable=Opdatering er tilgængelig.
@@ -433,6 +436,9 @@ main.vaultDetail.missing.info=Cryptomator kunne ikke finde en boks på denne sti
main.vaultDetail.missing.recheck=Kontrollér igen
main.vaultDetail.missing.remove=Fjern fra listen over bokse…
main.vaultDetail.missing.changeLocation=Skift placering for boks…
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=Bokskonfiguration mangler.
main.vaultDetail.missingVaultConfig.restore=Gendan bokskonfiguration
### Needs Migration
main.vaultDetail.migrateButton=Opgradér boks
main.vaultDetail.migratePrompt=Din boks skal opgraderes til et nyt format, før du kan tilgå den
@@ -497,6 +503,7 @@ vaultOptions.hub.convertBtn=Konvertér til adgangskodebaseret boks
recoveryKey.display.title=Vis gendannelsesnøgle
recoveryKey.create.message=Adgangskode krævet
recoveryKey.create.description=Indtast adgangskoden for "%s", for at vise gendannelsesnøglen.
recoveryKey.recover.description=Indtast adgangskoden for "%s" for at gendanne bokskonfiguration.
recoveryKey.display.description=Den følgende gendannelsesnøgle kan bruges til at genskabe adgang til "%s":
recoveryKey.display.StorageHints=Opbevar den et meget sikkert sted, som fx:\n • Gem den i din adgangskode-manager\n • Gem den på et USB-drev\n • Print den ud på papir
## Reset Password
@@ -509,9 +516,57 @@ recoveryKey.recover.invalidKey=Denne gendannelsesnøgle er ikke gyldig
recoveryKey.printout.heading=Cryptomator gendannelsesnøgle\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Nulstil
recoveryKey.recover.recoverBtn=Gendan
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Adgangskod nulstillet
recoveryKey.recover.resetSuccess.description=Du kan nu låse din boks op med den nye adgangskode.
### Recovery Key Vault Config Reset Success
recoveryKey.recover.resetMasterkeyFileSuccess.description=Du kan låse din boks op med din adgangskode nu.
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
recover.existing.title=Boks tilføjet
recover.existing.message=Boksen blev tilføjet med succes
recover.existing.description=Din boks "%s" er blevet tilføjet til bokslisten. Ingen genoprettelsesproces var nødvendig.
##Vault Already Exists - Dialog
recover.alreadyExists.title=Boksen findes allerede
recover.alreadyExists.message=Denne boks er allerede tilføjet
recover.alreadyExists.description=Din boks "%s" er allerede til stede i din boksliste og blev derfor ikke tilføjet igen.
##Invalid Selection - Dialog
recover.invalidSelection.title=Ugyldigt valg
recover.invalidSelection.message=Dit valg er ikke en boks
recover.invalidSelection.description=Den valgte mappe skal være en gyldig Cryptomator boks.
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub Boks
contactHubVaultOwner.message=Denne boks blev oprettet med Cryptomator Hub
contactHubVaultOwner.description=Tag venligst kontakt til boksenejer for at gendanne den manglende fil. De kan downloade boksskabelon fra Cryptomator Hub.
##Dialog Title
recover.recoverVaultConfig.title=Gendan bokskonfiguration
recover.recoverMasterkey.title=Gendan Masterkey
## OnBoarding
recover.onBoarding.chooseMethod=Vælg genoprettelsesmetode:
recover.onBoarding.useRecoveryKey=Brug gendannelsesnøgle
recover.onBoarding.usePassword=Brug adgangskode
recover.onBoarding.intro=Sørg for at tjekke følgende:
recover.onBoarding.pleaseConfirm=Før der fortsættes, bekræftes at:
recover.onBoarding.otherwisePleaseConfirm=I modsat fald bekræftes følgende:
recover.onBoarding.allMissing.intro=Hvis denne boks administreres af Cryptomator Hub, skal boksejer gendanne den for dig.
recover.onBoarding.intro.ensure=Alle filer er fuldt synkroniserede.
recover.onBoarding.affirmation=Jeg har læst og forstået disse krav
###Vault Config Missing
recover.onBoarding.intro.recoveryKey=Du har gendannelsesnøglen og ved, om ekspertindstillinger blev brugt.
recover.onBoarding.intro.password=Du har boksens adgangskode og ved, om ekspertindstillinger blev brugt.
###Masterkey Missing
recover.onBoarding.intro.masterkey.recoveryKey=Du har boksens gendannelsesnøgle.
## Expert Settings
recover.expertSettings.shorteningThreshold.title=Denne værdi skal matche den, der blev brugt før gendannelse for at sikre kompatibilitet med tidligere krypterede data.
# Convert Vault
convertVault.title=Konvertér boks
@@ -561,6 +616,7 @@ dokanySupportEnd.preferencesBtn=Åbn Indstillinger
retryIfReadonly.title=Begrænset boksadgang
retryIfReadonly.message=Ingen skriveadgang til boksens mappe
retryIfReadonly.description=Cryptomator kan ikke skrive til boksens mappe. Du kan ændre boksen til at være skrivebeskyttet og prøve igen. Denne indstilling kan være deaktiveret i boksens indstillinger.
retryIfReadonly.retry=Skift og prøv igen
# Share Vault
shareVault.title=Del Boks
@@ -584,10 +640,39 @@ shareVault.hub.openHub=Åben Cryptomator Hub
# Decrypt File Names
decryptNames.title=Dekryptér Filnavne
decryptNames.filePicker.title=Vælg krypteret fil
decryptNames.filePicker.extensionDescription=Cryptomator krypteret fil
decryptNames.copyTable.tooltip=Kopiér tabel
decryptNames.clearTable.tooltip=Ryd tabel
decryptNames.copyHint=Kopiér celleindhold med %s
decryptNames.dropZone.message=Slip filer eller klik for at vælge
decryptNames.dropZone.error.vaultInternalFiles=Boks interne filer med intet dekrypterbart navn valgt
decryptNames.dropZone.error.foreignFiles=Filer hører ikke til boksen "%s"
decryptNames.dropZone.error.noDirIdBackup=Mappe af valgte filer indeholder ikke dirId.c9r fil
decryptNames.dropZone.error.generic=Kunne ikke dekryptere filnavne
# Event View
eventView.title=Begivenheder
eventView.filter.allVaults=Alle
eventView.clearListButton.tooltip=Ryd liste
## event list entries
eventView.entry.vaultLocked.description=Lås "%s" op for detaljer
eventView.entry.conflictResolved.message=Løst konflikt
eventView.entry.conflictResolved.showDecrypted=Vis dekrypteret fil
eventView.entry.conflictResolved.copyDecrypted=Kopiér dekrypteret sti
eventView.entry.conflict.message=Konfliktløsning mislykkedes
eventView.entry.conflict.showDecrypted=Vis dekrypteret, original fil
eventView.entry.conflict.copyDecrypted=Kopier dekrypteret, original sti
eventView.entry.conflict.showEncrypted=Vis modstridende, krypteret fil
eventView.entry.conflict.copyEncrypted=Kopier modstridende, krypteret sti
eventView.entry.decryptionFailed.message=Dekryptering mislykkedes
eventView.entry.decryptionFailed.showEncrypted=Vis krypteret fil
eventView.entry.decryptionFailed.copyEncrypted=Kopiér krypteret sti
eventView.entry.brokenDirFile.message=Brudt mappelink
eventView.entry.brokenDirFile.showEncrypted=Vis brudt, krypteret link
eventView.entry.brokenDirFile.copyEncrypted=Kopiér sti til brudt link
eventView.entry.brokenFileNode.message=Brudt filsystem-node
eventView.entry.brokenFileNode.showEncrypted=Vis brudt, krypteret node
eventView.entry.brokenFileNode.copyEncrypted=Kopiér sti af brudt, krypteret node
eventView.entry.brokenFileNode.copyDecrypted=Kopiér dekrypteret sti
+59 -2
View File
@@ -96,6 +96,7 @@ addvault.new.readme.accessLocation.4=Du kannst diese Datei löschen.
## Existing
addvaultwizard.existing.title=Bestehenden Tresor hinzufügen
addvaultwizard.existing.instruction=Wähle die Datei „vault.cryptomator“ deines bestehenden Tresors aus. Falls nur eine Datei mit der Bezeichnung „masterkey.cryptomator“ vorhanden ist, nutze stattdessen diese.
addvaultwizard.existing.restore=Wiederherstellen…
addvaultwizard.existing.chooseBtn=Durchsuchen …
addvaultwizard.existing.filePickerTitle=Tresordatei auswählen
addvaultwizard.existing.filePickerMimeDesc=Cryptomator-Tresor
@@ -127,6 +128,7 @@ unlock.unlockBtn=Entsperren
## Select
unlock.chooseMasterkey.message=Masterkey-Datei nicht gefunden
unlock.chooseMasterkey.description=Cryptomator konnte die Masterkey-Datei des Tresors „%s“ nicht finden. Bitte wähle die Datei manuell aus.
unlock.chooseMasterkey.restoreInstead=Masterkey Datei wiederherstellen
unlock.chooseMasterkey.filePickerTitle=Masterkey-Datei auswählen
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator-Masterkey
## Success
@@ -394,8 +396,9 @@ main.vaultlist.contextMenu.unlockNow=Jetzt entsperren
main.vaultlist.contextMenu.vaultoptions=Tresoroptionen anzeigen
main.vaultlist.contextMenu.reveal=Laufwerk anzeigen
main.vaultlist.contextMenu.share=Teilen …
main.vaultlist.addVaultBtn.menuItemNew=Neuen Tresor erstellen...
main.vaultlist.addVaultBtn.menuItemExisting=Bestehenden Tresor öffnen...
main.vaultlist.addVaultBtn.menuItemNew=Neuen Tresor erstellen
main.vaultlist.addVaultBtn.menuItemExisting=Bestehenden Tresor öffnen
main.vaultlist.addVaultBtn.menuItemRecover=Bestehenden Tresor wiederherstellen…
main.vaultlist.showEventsButton.tooltip=Ereignis-Ansicht öffnen
##Notificaition
main.notification.updateAvailable=Eine neue Version ist verfügbar.
@@ -433,6 +436,9 @@ main.vaultDetail.missing.info=Cryptomator konnte keinen Tresor mit diesem Pfad f
main.vaultDetail.missing.recheck=Erneut prüfen
main.vaultDetail.missing.remove=Aus Tresorliste entfernen …
main.vaultDetail.missing.changeLocation=Speicherort des Tresors ändern …
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=Tresorkonfiguration fehlt.
main.vaultDetail.missingVaultConfig.restore=Tresorkonfiguration wiederherstellen
### Needs Migration
main.vaultDetail.migrateButton=Tresor upgraden
main.vaultDetail.migratePrompt=Dein Tresor muss in ein neues Format konvertiert werden, bevor du auf ihn zugreifen kannst
@@ -497,6 +503,7 @@ vaultOptions.hub.convertBtn=In passwortgeschützten Tresor umwandeln
recoveryKey.display.title=Wiederherstellungsschlüssel anzeigen
recoveryKey.create.message=Passwort erforderlich
recoveryKey.create.description=Gib das Passwort für „%s“ ein, um dessen Wiederherstellungsschlüssel anzuzeigen.
recoveryKey.recover.description=Gib das Passwort für "%s" ein, um die Tresorkonfiguration wiederherzustellen.
recoveryKey.display.description=Mit folgendem Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
recoveryKey.display.StorageHints=Bewahre ihn möglichst sicher auf, z. B.\n • in einem Passwortmanager\n • auf einem USB-Speicherstick\n • auf Papier ausgedruckt
## Reset Password
@@ -509,9 +516,59 @@ recoveryKey.recover.invalidKey=Dieser Wiederherstellungsschlüssel ist ungültig
recoveryKey.printout.heading=Cryptomator-Wiederherstellungsschlüssel\n„%s“\n
### Reset Password
recoveryKey.recover.resetBtn=Zurücksetzen
recoveryKey.recover.recoverBtn=Wiederherstellen
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Passwort erfolgreich zurückgesetzt
recoveryKey.recover.resetSuccess.description=Du kannst deinen Tresor mit dem neuen Passwort entsperren.
### Recovery Key Vault Config Reset Success
recoveryKey.recover.resetVaultConfigSuccess.message=Tresorkonfiguration wiederhergestellt
recoveryKey.recover.resetMasterkeyFileSuccess.message=Masterkey-Datei wiederhergestellt
recoveryKey.recover.resetMasterkeyFileSuccess.description=Du kannst deinen Tresor mit dem neuen Passwort jetzt entsperren.
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
recover.existing.title=Tresor hinzugefügt
recover.existing.message=Der Tresor wurde erfolgreich hinzugefügt
recover.existing.description=Dein Tresor "%s" wurde zur Tresorliste hinzugefügt. Es war kein Wiederherstellungsprozess nötig.
##Vault Already Exists - Dialog
recover.alreadyExists.title=Tresor existiert bereits
recover.alreadyExists.message=Dieser Tresor wurde bereits hinzugefügt
recover.alreadyExists.description=Dein Tresor "%s" ist bereits in der Tresorliste vorhanden und wurde daher nicht erneut hinzugefügt.
##Invalid Selection - Dialog
recover.invalidSelection.title=Ungültige Auswahl
recover.invalidSelection.message=Der ausgewählte Ordner ist kein Tresor
recover.invalidSelection.description=Der ausgewählte Ordner muss ein gültiger Cryptomator-Tresor sein.
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hubtresor
contactHubVaultOwner.message=Dieser Tresor wurde mit Cryptomator Hub erstellt
contactHubVaultOwner.description=Bitte wenden dich an den Tresorbesitzer, um die fehlende Datei wiederherzustellen. Dieser kann die Tresorvorlage von Cryptomator Hub herunterladen.
##Dialog Title
recover.recoverVaultConfig.title=Tresorkonfiguration wiederherstellen
recover.recoverMasterkey.title=Masterkey wiederherstellen
## OnBoarding
recover.onBoarding.chooseMethod=Wähle die Wiederherstellungsmethode:
recover.onBoarding.useRecoveryKey=Wiederherstellungsschlüssel verwenden
recover.onBoarding.usePassword=Passwort verwenden
recover.onBoarding.intro=Stelle sicher, folgendes geprüft zu haben:
recover.onBoarding.pleaseConfirm=Bevor es weiter geht, bestätige bitte:
recover.onBoarding.otherwisePleaseConfirm=Andernfalls, bestätige bitte:
recover.onBoarding.allMissing.intro=Wird der Tresor von Cryptomator Hub verwaltet, muss der Tresorbesitzer ihn für dich wiederherstellen.
recover.onBoarding.intro.ensure=Alle Dateien sind vollständig synchronisiert.
recover.onBoarding.affirmation=Ich habe die Anforderungen gelesen und verstanden
###Vault Config Missing
recover.onBoarding.intro.recoveryKey=Du besitzt den Wiederherstellungsschlüssel und weißt, ob Experteneinstellungen verwendet wurden.
recover.onBoarding.intro.password=Du besitzt das Tresorpasswort und weißt, ob Experteneinstellungen verwendet wurden.
###Masterkey Missing
recover.onBoarding.intro.masterkey.recoveryKey=Du besitzt den Wiederherstellungsschlüssel.
## Expert Settings
recover.expertSettings.shorteningThreshold.title=Um Kompatibilität mit den verschlüsselten Dateien zu gewährleisten, muss der gleiche Wert wie zur Tresorerstellung verwendet werden.
# Convert Vault
convertVault.title=Tresor umwandeln
+21 -2
View File
@@ -394,8 +394,6 @@ main.vaultlist.contextMenu.unlockNow=Ξεκλείδωμα τώρα
main.vaultlist.contextMenu.vaultoptions=Εμφάνιση επιλογών Vault
main.vaultlist.contextMenu.reveal=Αποκάλυψη εικονικού δίσκου
main.vaultlist.contextMenu.share=Κοινοποίηση…
main.vaultlist.addVaultBtn.menuItemNew=Δημιουργία Νέας Κρύπτης...
main.vaultlist.addVaultBtn.menuItemExisting=Άνοιγμα Υπάρχοντος Κρύπτης...
main.vaultlist.showEventsButton.tooltip=Άνοιγμα προβολής συμβάντων
##Notificaition
main.notification.updateAvailable=Η ενημέρωση είναι διαθέσιμη.
@@ -433,6 +431,7 @@ main.vaultDetail.missing.info=Cryptomator δεν βρήκε vault σε αυτό
main.vaultDetail.missing.recheck=Επανέλεγχος
main.vaultDetail.missing.remove=Κατάργηση από την λίστα των Vault…
main.vaultDetail.missing.changeLocation=Αλλαγή τοποθεσίας Vault…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Αναβάθμιση Vault
main.vaultDetail.migratePrompt=Το vault σας πρέπει να αναβαθμιστεί σε νέα μορφή, προτού να έχετε πρόσβαση σε αυτό
@@ -512,6 +511,26 @@ recoveryKey.recover.resetBtn=Επαναφορά
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Επιτυχής επαναφορά κωδικού πρόσβασης
recoveryKey.recover.resetSuccess.description=Μπορείτε να ξεκλειδώσετε την κρύπτη σας με το νέο κωδικό πρόσβασης.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Κρύπτη Hub
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Μετατροπή Θησαυ/κιου
+29 -2
View File
@@ -96,6 +96,7 @@ addvault.new.readme.accessLocation.4=No dude en eliminar este archivo.
## Existing
addvaultwizard.existing.title=Añadir bóveda existente
addvaultwizard.existing.instruction=Elija el archivo "vault.cryptomator" de su bóveda existente. Si solo existe un archivo llamado "masterkey.cryptomator", selecciónelo en su lugar.
addvaultwizard.existing.restore=
addvaultwizard.existing.chooseBtn=Elegir…
addvaultwizard.existing.filePickerTitle=Seleccionar archivo de bóveda
addvaultwizard.existing.filePickerMimeDesc=Bóveda de Cryptomator
@@ -127,6 +128,7 @@ unlock.unlockBtn=Desbloquear
## Select
unlock.chooseMasterkey.message=Archivo de la clave maestra no encontrado
unlock.chooseMasterkey.description=No se pudo encontrar el archivo de la clave maestra para la bóveda "%s". Por favor, elija manualmente el archivo de la clave.
unlock.chooseMasterkey.restoreInstead=
unlock.chooseMasterkey.filePickerTitle=Seleccione el archivo de la clave maestra
unlock.chooseMasterkey.filePickerMimeDesc=Clave maestra de Cryptomator
## Success
@@ -394,8 +396,9 @@ main.vaultlist.contextMenu.unlockNow=Desbloquear ahora
main.vaultlist.contextMenu.vaultoptions=Mostrar opciones de la bóveda
main.vaultlist.contextMenu.reveal=Revelar unidad
main.vaultlist.contextMenu.share=Compartir…
main.vaultlist.addVaultBtn.menuItemNew=Crear Bóveda Nueva...
main.vaultlist.addVaultBtn.menuItemExisting=Abrir Bóveda Existente...
main.vaultlist.addVaultBtn.menuItemNew=
main.vaultlist.addVaultBtn.menuItemExisting=
main.vaultlist.addVaultBtn.menuItemRecover=
main.vaultlist.showEventsButton.tooltip=Abrir vista de evento
##Notificaition
main.notification.updateAvailable=Existen actualizaciones disponibles.
@@ -433,6 +436,9 @@ main.vaultDetail.missing.info=Cryptomator no pudo encontrar una bóveda en esta
main.vaultDetail.missing.recheck=Volver a comprobar
main.vaultDetail.missing.remove=Eliminar de la lista de bóveda…
main.vaultDetail.missing.changeLocation=Cambiar ubicación de la bóveda…
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=Falta la configuración de la bóveda.
main.vaultDetail.missingVaultConfig.restore=
### Needs Migration
main.vaultDetail.migrateButton=Actualizar bóveda
main.vaultDetail.migratePrompt=Su bóveda necesita ser actualizada a un formato nuevo antes de poder acceder a ella
@@ -497,6 +503,7 @@ vaultOptions.hub.convertBtn=Convertir a Bóveda con Contraseña
recoveryKey.display.title=Mostrar clave de recuperación
recoveryKey.create.message=Contraseña requerida
recoveryKey.create.description=Ingresar la contraseña para mostrar la clave de recuperación para "%s":
recoveryKey.recover.description=
recoveryKey.display.description=La siguiente clave de recuperación puede usarse para restaurar el acceso a "%s":
recoveryKey.display.StorageHints=Manténgala en algún lugar seguro, p.ej.:\n • Almacenarla en un administrador de contraseñas\n • Guardarla en una llave USB\n • Imprimirla en un papel
## Reset Password
@@ -512,6 +519,26 @@ recoveryKey.recover.resetBtn=## Reiniciar contraseña\nrecoveryKey.recover.reset
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Contraseña restablecida con éxito
recoveryKey.recover.resetSuccess.description=Puede desbloquear su bóveda con la contraseña nueva.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Bóveda de Hub
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Convertir bóveda
@@ -183,6 +183,7 @@ main.vaultlist.contextMenu.reveal=نمایش درایو
main.vaultDetail.revealBtn=نمایش درایو
main.vaultDetail.lockBtn=قفل
### Missing
### Missing Vault Config
### Needs Migration
### Error
@@ -204,6 +205,25 @@ vaultOptions.mount.mountPoint.directoryPickerButton=انتخاب کنید…
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
+20 -2
View File
@@ -393,8 +393,6 @@ main.vaultlist.contextMenu.unlockNow=Avaa Nyt
main.vaultlist.contextMenu.vaultoptions=Näytä holvin asetukset
main.vaultlist.contextMenu.reveal=Paljasta Asema
main.vaultlist.contextMenu.share=Jaa…
main.vaultlist.addVaultBtn.menuItemNew=Luo uusi holvi...
main.vaultlist.addVaultBtn.menuItemExisting=Avaa olemassa oleva holvi...
main.vaultlist.showEventsButton.tooltip=Avaa tapahtumanäkymä
##Notificaition
main.notification.updateAvailable=Päivitys on saatavilla.
@@ -432,6 +430,7 @@ main.vaultDetail.missing.info=Cryptomator ei löytänyt täältä holvia.
main.vaultDetail.missing.recheck=Tarkista uudelleen
main.vaultDetail.missing.remove=Poista holvilistalta…
main.vaultDetail.missing.changeLocation=Vaihda holvin sijaintia…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Päivitä Holvi
main.vaultDetail.migratePrompt=Holvisi täytyy muuntaa uuteen muotoon ennen kuin voit avata sen
@@ -511,6 +510,25 @@ recoveryKey.recover.resetBtn=Palauta
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Salasanan palautus onnistui
recoveryKey.recover.resetSuccess.description=Voit avata holvin uudella salasanalla.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Muunna holvi
@@ -416,6 +416,7 @@ main.vaultDetail.missing.info=Hindi makahanap ng vault ang Cryptomator sa landas
main.vaultDetail.missing.recheck=Suriin muli
main.vaultDetail.missing.remove=Alisin sa Listahan ng Vault…
main.vaultDetail.missing.changeLocation=Baguhin ang Lokasyon ng Vault…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=I-upgrade ang Vault
main.vaultDetail.migratePrompt=Kailangang i-upgrade ang iyong vault sa bagong format, bago mo ito ma-access
@@ -495,6 +496,26 @@ recoveryKey.recover.resetBtn=I-reset
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Matagumpay ang pag-reset ng password
recoveryKey.recover.resetSuccess.description=Maaari mong i-unlock ang iyong vault gamit ang bagong password.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub Vault
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=I-convert ang Vault
+57 -2
View File
@@ -96,6 +96,7 @@ addvault.new.readme.accessLocation.4=Vous pouvez supprimer ce fichier.
## Existing
addvaultwizard.existing.title=Ajouter un coffre existant
addvaultwizard.existing.instruction=Choisissez le fichier « vault.cryptomator » de votre volume existant. Si seul le fichier « masterkey.cryptomator » est présent, sélectionnez celui-là.
addvaultwizard.existing.restore=Restaurer…
addvaultwizard.existing.chooseBtn=Choisir…
addvaultwizard.existing.filePickerTitle=Sélectionnez le fichier correspondant au volume chiffré
addvaultwizard.existing.filePickerMimeDesc=Coffre-fort Cryptomator
@@ -127,6 +128,7 @@ unlock.unlockBtn=Déverrouiller
## Select
unlock.chooseMasterkey.message=Fichier Masterkey introuvable
unlock.chooseMasterkey.description=Impossible de trouver le fichier clef à l'adresse attendue pour l'espace chiffré "%s". Veuillez sélectionner le fichier clef manuellement.
unlock.chooseMasterkey.restoreInstead=Restaurez le fichier masterkey à la place
unlock.chooseMasterkey.filePickerTitle=Sélectionner le fichier clef
unlock.chooseMasterkey.filePickerMimeDesc=Clé principale Cryptomator
## Success
@@ -394,8 +396,9 @@ main.vaultlist.contextMenu.unlockNow=Déverrouiller maintenant
main.vaultlist.contextMenu.vaultoptions=Afficher les options du volume chiffré
main.vaultlist.contextMenu.reveal=Afficher le lecteur
main.vaultlist.contextMenu.share=Partager…
main.vaultlist.addVaultBtn.menuItemNew=Créer un nouveau coffre...
main.vaultlist.addVaultBtn.menuItemExisting=Ouvrir un coffre existant...
main.vaultlist.addVaultBtn.menuItemNew=Créer un nouveau coffre
main.vaultlist.addVaultBtn.menuItemExisting=Ouvrir le coffre existant
main.vaultlist.addVaultBtn.menuItemRecover=Restaurer le coffre existant…
main.vaultlist.showEventsButton.tooltip=Ouvrir la vue Événements
##Notificaition
main.notification.updateAvailable=Mise à jour disponible.
@@ -433,6 +436,9 @@ main.vaultDetail.missing.info=Cryptomator n'a pas pu trouver de volume chiffré
main.vaultDetail.missing.recheck=Revérifier
main.vaultDetail.missing.remove=Retirer de la liste des volumes…
main.vaultDetail.missing.changeLocation=Changer l'emplacement du volume…
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=La configuration du coffre est manquante.
main.vaultDetail.missingVaultConfig.restore=Restaurer la configuration du coffre
### Needs Migration
main.vaultDetail.migrateButton=Mettre le volume à jour
main.vaultDetail.migratePrompt=Votre coffre doit être converti dans un nouveau format avant d'y accéder
@@ -497,6 +503,7 @@ vaultOptions.hub.convertBtn=Convertir en coffre-fort basé sur mot de passe
recoveryKey.display.title=Montrer la clé de récupération
recoveryKey.create.message=Mot de passe requis
recoveryKey.create.description=Entrez le mot de passe de "%s" pour afficher sa clé de récupération.
recoveryKey.recover.description=Entrez le mot de passe pour "%s" pour restaurer la configuration du coffre.
recoveryKey.display.description=La clé de récupération suivante peut être utilisée pour restaurer l'accès à "%s " :
recoveryKey.display.StorageHints=Gardez-la dans un endroit sûr, par ex. :\n • Stockez-la en utilisant un gestionnaire de mots de passe\n • Enregistrez-la sur une clé USB\n • Imprimez-la
## Reset Password
@@ -509,9 +516,57 @@ recoveryKey.recover.invalidKey=Cette clé de récupération n'est pas valide
recoveryKey.printout.heading=Clé de récupération Cryptomator "%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Réinitialiser
recoveryKey.recover.recoverBtn=Restaurer
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Réinitialisation du mot de passe réussie
recoveryKey.recover.resetSuccess.description=Vous pouvez déverrouiller votre coffre avec le nouveau mot de passe.
### Recovery Key Vault Config Reset Success
recoveryKey.recover.resetMasterkeyFileSuccess.description=Vous pouvez maintenant déverrouiller votre coffre avec votre mot de passe.
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
recover.existing.title=Coffre ajouté
recover.existing.message=Le coffre a été ajouté avec succès
recover.existing.description=Votre coffre "%s" a été ajouté à la liste des coffres. Aucun processus de récupération n'a été nécessaire.
##Vault Already Exists - Dialog
recover.alreadyExists.title=Le coffre-fort existe déjà
recover.alreadyExists.message=Ce coffre a déjà été ajouté
recover.alreadyExists.description=Votre coffre «%s» est déjà présent dans votre liste de coffres et n'a donc pas été ajouté à nouveau.
##Invalid Selection - Dialog
recover.invalidSelection.title=Sélection invalide
recover.invalidSelection.message=Votre sélection n'est pas un coffre
recover.invalidSelection.description=Le dossier sélectionné doit être un coffre Cryptomator valide.
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Coffre dans Hub
contactHubVaultOwner.message=Ce coffre a été créé avec le Hub Cryptomator
contactHubVaultOwner.description=.
##Dialog Title
recover.recoverVaultConfig.title=Récupérer la configuration du coffre
recover.recoverMasterkey.title=Récupérer la clé principale
## OnBoarding
recover.onBoarding.chooseMethod=Choisissez la méthode de récupération :
recover.onBoarding.useRecoveryKey=Utiliser la clé de récupération
recover.onBoarding.usePassword=Utiliser mot de passe
recover.onBoarding.intro=Assurez-vous de vérifier ce qui suit :
recover.onBoarding.pleaseConfirm=Avant de procéder, veuillez confirmer ceci :
recover.onBoarding.otherwisePleaseConfirm=Sinon, veuillez confirmer ceci:
recover.onBoarding.allMissing.intro=Si ce coffre est géré par Cryptomator Hub, le propriétaire du coffre doit le restaurer pour vous.
recover.onBoarding.intro.ensure=.
recover.onBoarding.affirmation=J'ai lu et compris ces prérequis
###Vault Config Missing
recover.onBoarding.intro.recoveryKey=Vous avez la clé de récupération et et savez si les paramètres experts ont été utilisés.
recover.onBoarding.intro.password=Vous avez le mot de passe du coffre et savez si les paramètres experts ont été utilisés.
###Masterkey Missing
recover.onBoarding.intro.masterkey.recoveryKey=Vous avez la clé de récupération du coffre.
## Expert Settings
recover.expertSettings.shorteningThreshold.title=
# Convert Vault
convertVault.title=Convertir le coffre
@@ -101,6 +101,7 @@ lock.forced.retryBtn=Tentar de novo
### Locked
### Unlocked
### Missing
### Missing Vault Config
### Needs Migration
### Error
@@ -119,6 +120,25 @@ lock.forced.retryBtn=Tentar de novo
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
@@ -384,6 +384,7 @@ main.vaultDetail.missing.info=Cryptomator לא הצליח למצוא כספת ב
main.vaultDetail.missing.recheck=בדיקה נוספת
main.vaultDetail.missing.remove=הסר מרשימה הכספות…
main.vaultDetail.missing.changeLocation=שנה את מיקום הכספת…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=שדרג הכספת
main.vaultDetail.migratePrompt=צריך לשדרג את הכספת שלך לגרסה חדשה לפני שניתן לגשת אליה
@@ -455,6 +456,25 @@ recoveryKey.recover.resetBtn=איפוס
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=איפוס סיסמה הצליח
recoveryKey.recover.resetSuccess.description=ניתן לפתוח את הכספת עם הסיסמה החדשה.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.convert.convertBtn.before=להמיר
@@ -256,6 +256,7 @@ main.vaultDetail.lockBtn=लॉक करें
main.vaultDetail.stats=वॉल्ट के आंकड़े
### Missing
main.vaultDetail.missing.remove=वॉल्ट की सूची से हटाए।
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=वाउल्ट को अपग्रेड करें
### Error
@@ -291,6 +292,25 @@ recoveryKey.create.description=रिकवरी-की दिखाने क
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
@@ -298,6 +298,7 @@ main.vaultDetail.missing.info=Cryptomator nije mogao pronaći trezor s ovom puta
main.vaultDetail.missing.recheck=Ponovo provjeri
main.vaultDetail.missing.remove=Ukloni iz liste trezora…
main.vaultDetail.missing.changeLocation=Promijeni lokaciju trezora…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Nadogradi trezor
main.vaultDetail.migratePrompt=Vaš trezor treba se nadograditi na novi format, prije nego mu možete pristupiti
@@ -357,6 +358,25 @@ recoveryKey.recover.correctKey=Ovo je valjani ključ za oporavak
recoveryKey.printout.heading=Cryptomator-ov ključ za oporavak\n"%s"\n
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
+21 -2
View File
@@ -394,8 +394,6 @@ main.vaultlist.contextMenu.unlockNow=Azonnali feloldás
main.vaultlist.contextMenu.vaultoptions=Széf beállítások
main.vaultlist.contextMenu.reveal=Széf megjelenítése
main.vaultlist.contextMenu.share=Megosztás…
main.vaultlist.addVaultBtn.menuItemNew=Új széf létrehozása...
main.vaultlist.addVaultBtn.menuItemExisting=Meglévő széf megnyitása...
##Notificaition
main.notification.updateAvailable=Frissítés elérhető.
main.notification.support=Cryptomator támogatása.
@@ -429,6 +427,7 @@ main.vaultDetail.missing.info=A Cryptomator nem talált széfet ezen az útvonal
main.vaultDetail.missing.recheck=Ellenőrizze újra
main.vaultDetail.missing.remove=A széf eltávolítása a listából…
main.vaultDetail.missing.changeLocation=A széf helyének megváltoztatása…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Széf frissítése
main.vaultDetail.migratePrompt=A széfet új formátumra kell frissíteni, mielőtt hozzáférhet
@@ -508,6 +507,26 @@ recoveryKey.recover.resetBtn=Visszaállítás
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=A jelszó alaphelyzetbe állítása sikeresen megtörtént
recoveryKey.recover.resetSuccess.description=Feloldhatja a széfet az új jelszóval.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub széf
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Széf átalakítása
+21 -2
View File
@@ -394,8 +394,6 @@ main.vaultlist.contextMenu.unlockNow=Buka Kunci Sekarang
main.vaultlist.contextMenu.vaultoptions=Tampilkan Opsi Vault
main.vaultlist.contextMenu.reveal=Buka Drive
main.vaultlist.contextMenu.share=Bagikan…
main.vaultlist.addVaultBtn.menuItemNew=Buat Vault Baru...
main.vaultlist.addVaultBtn.menuItemExisting=Buka Vault yang Tersedia...
##Notificaition
main.notification.updateAvailable=Pembaruan tersedia.
main.notification.support=Dukung Cryptomator.
@@ -429,6 +427,7 @@ main.vaultDetail.missing.info=Cryptomator tidak dapat menemukan vault di path in
main.vaultDetail.missing.recheck=Periksa kembali
main.vaultDetail.missing.remove=Hapus dari Daftar Vault…
main.vaultDetail.missing.changeLocation=Ganti Lokasi Vault…
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Tingkatkan Vault
main.vaultDetail.migratePrompt=Vault Anda perlu ditingkatkan ke format baru, sebelum Anda dapat mengaksesnya
@@ -508,6 +507,26 @@ recoveryKey.recover.resetBtn=Atur ulang
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Atur ulang kata sandi berhasil
recoveryKey.recover.resetSuccess.description=Anda dapat membuka kunci vault Anda dengan kata sandi baru.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub Vault
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Konversi Vault
+57 -2
View File
@@ -96,6 +96,7 @@ addvault.new.readme.accessLocation.4=Sentiti libero di rimuovere questo file.
## Existing
addvaultwizard.existing.title=Aggiungi una Cassaforte Esistente
addvaultwizard.existing.instruction=Scegliere il file "vault.cryptomator" della tua cassaforte. Se esiste solo un file chiamato "masterkey.cryptomator", allora scegli quello.
addvaultwizard.existing.restore=Ripristina…
addvaultwizard.existing.chooseBtn=Scegli…
addvaultwizard.existing.filePickerTitle=Seleziona file cassaforte
addvaultwizard.existing.filePickerMimeDesc=Cassaforte di Cryptomator
@@ -127,6 +128,7 @@ unlock.unlockBtn=Sblocca
## Select
unlock.chooseMasterkey.message=File Masterkey non trovato
unlock.chooseMasterkey.description=Impossibile trovare il file Masterkey per questa cassaforte alla sua posizione prevista. Sei pregato di sceglierlo manualmente.
unlock.chooseMasterkey.restoreInstead=Ripristina invece il file con la chiave principale
unlock.chooseMasterkey.filePickerTitle=Seleziona il File Masterkey
unlock.chooseMasterkey.filePickerMimeDesc=Chiave principale di Cryptomator
## Success
@@ -394,8 +396,9 @@ main.vaultlist.contextMenu.unlockNow=Sblocca Ora
main.vaultlist.contextMenu.vaultoptions=Mostra le Opzioni della Cassaforte
main.vaultlist.contextMenu.reveal=Rivela Unità
main.vaultlist.contextMenu.share=Condividi…
main.vaultlist.addVaultBtn.menuItemNew=Crea una nuova cassaforte...
main.vaultlist.addVaultBtn.menuItemExisting=Apri una cassaforte esistente...
main.vaultlist.addVaultBtn.menuItemNew=Crea una nuova cassaforte
main.vaultlist.addVaultBtn.menuItemExisting=Apri una cassaforte esistente
main.vaultlist.addVaultBtn.menuItemRecover=Recupera una cassaforte esistente…
main.vaultlist.showEventsButton.tooltip=Apri vista eventi
##Notificaition
main.notification.updateAvailable=Aggiornamento disponibile.
@@ -433,6 +436,9 @@ main.vaultDetail.missing.info=Cryptomator non è riuscito a trovare una cassafor
main.vaultDetail.missing.recheck=Ricontrolla
main.vaultDetail.missing.remove=Rimuovi dall'elenco delle casseforti…
main.vaultDetail.missing.changeLocation=Cambia la Posizione della Cassaforte…
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=Manca la configurazione della cassaforte.
main.vaultDetail.missingVaultConfig.restore=Ripristina la configurazione della cassaforte
### Needs Migration
main.vaultDetail.migrateButton=Aggiorna la Cassaforte
main.vaultDetail.migratePrompt=La tua cassaforte dev'esser aggiornata a un nuovo formato, prima di potervi accedere
@@ -497,6 +503,7 @@ vaultOptions.hub.convertBtn=Converti in cassaforte basata su password
recoveryKey.display.title=Mostra Chiave Di Recupero
recoveryKey.create.message=Password richiesta
recoveryKey.create.description=Inserisci la password per visualizzare la chiave di recupero per "%s":
recoveryKey.recover.description=Inserisci la password di "%s" per recuperare la configurazione della cassaforte.
recoveryKey.display.description=La seguente chiave di recupero può essere utilizzata per ripristinare l'accesso a %s":
recoveryKey.display.StorageHints=Conservala da qualche parte in modo sicuro, es.\n • Archiviarla usando un gestore di password\n • Salvarla su un'unità flash USB\n • Stamparla su carta
## Reset Password
@@ -509,9 +516,57 @@ recoveryKey.recover.invalidKey=Questa chiave di recupero non é valida
recoveryKey.printout.heading=Chiave di recupero Cryptomator\n"%s"\n
### Reset Password
recoveryKey.recover.resetBtn=Reimposta
recoveryKey.recover.recoverBtn=Recupera
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Password reimpostata correttamente
recoveryKey.recover.resetSuccess.description=Puoi sbloccare la tua cassaforte con la nuova password.
### Recovery Key Vault Config Reset Success
recoveryKey.recover.resetMasterkeyFileSuccess.description=Ora puoi sbloccare la cassaforte con la tua password.
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
recover.existing.title=Cassaforte aggiunta
recover.existing.message=La cassaforte è stata aggiunta con successo
recover.existing.description=La tua cassaforte "%s" è stata aggiunta alla lista. Non è stata necessaria alcuna azione di recupero.
##Vault Already Exists - Dialog
recover.alreadyExists.title=La cassaforte esiste già
recover.alreadyExists.message=Questa cassaforte è già stata aggiunta
recover.alreadyExists.description=La tua cassaforte "%s" è già presente nella lista, quindi non è stata aggiunta di nuovo.
##Invalid Selection - Dialog
recover.invalidSelection.title=Selezione non valida
recover.invalidSelection.message=Quello che hai selezionato non è una cassaforte
recover.invalidSelection.description=La cartella selezionata deve essere una cassaforte Cryptomator.
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Centrale delle Casseforti
contactHubVaultOwner.message=Questa cassaforte è stata creata con Cryptomator Hub
contactHubVaultOwner.description=Contatta il proprietario della cassaforte per ripristinare il file mancante. Si può scaricare il modello di cassaforte da Cryptomator Hub.
##Dialog Title
recover.recoverVaultConfig.title=Ripristina la configurazione della cassaforte
recover.recoverMasterkey.title=Recupera il file con la chiave principale
## OnBoarding
recover.onBoarding.chooseMethod=Scegli metodo di recupero:
recover.onBoarding.useRecoveryKey=Usa la chiave di recupero
recover.onBoarding.usePassword=Utilizza la password
recover.onBoarding.intro=Assicurati di controllare quanto segue:
recover.onBoarding.pleaseConfirm=Prima di procedere, conferma che:
recover.onBoarding.otherwisePleaseConfirm=In caso contrario, conferma che:
recover.onBoarding.allMissing.intro=Se questa cassaforte è gestita da Cryptomator Hub, è il proprietario della cassaforte che deve ripristinarla.
recover.onBoarding.intro.ensure=Tutti i file sono completamente sincronizzati.
recover.onBoarding.affirmation=Ho letto e compreso questi requisiti
###Vault Config Missing
recover.onBoarding.intro.recoveryKey=Hai la chiave di recupero e sai se sono state utilizzate configurazioni avanzate.
recover.onBoarding.intro.password=Hai la chiave di recupero e sai se sono state utilizzate configurazioni avanzate.
###Masterkey Missing
recover.onBoarding.intro.masterkey.recoveryKey=Hai la chiave di ripristino della cassaforte.
## Expert Settings
recover.expertSettings.shorteningThreshold.title=Questo valore deve corrispondere a quello usato prima del recupero per garantire la compatibilità con i dati precedentemente crittografati.
# Convert Vault
convertVault.title=Convertire Cassaforte
+27 -2
View File
@@ -391,8 +391,6 @@ main.vaultlist.contextMenu.unlockNow=今すぐ解錠
main.vaultlist.contextMenu.vaultoptions=金庫のオプションを表示
main.vaultlist.contextMenu.reveal=ドライブを表示
main.vaultlist.contextMenu.share=共有…
main.vaultlist.addVaultBtn.menuItemNew=新しい金庫を作成…
main.vaultlist.addVaultBtn.menuItemExisting=既存の金庫を開く…
##Notificaition
main.notification.updateAvailable=アップデートがあります。
main.notification.support=Cryptomator を支援する。
@@ -429,6 +427,7 @@ main.vaultDetail.missing.info=Cryptomator はこの場所に金庫を見つけ
main.vaultDetail.missing.recheck=再確認
main.vaultDetail.missing.remove=金庫のリストから削除...
main.vaultDetail.missing.changeLocation=金庫の場所を変更...
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=金庫をアップグレード
main.vaultDetail.migratePrompt=金庫にアクセスする前に、 金庫を新しい形式にアップグレードする必要があります
@@ -508,6 +507,32 @@ recoveryKey.recover.resetBtn=リセット
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=パスワードをリセットしました
recoveryKey.recover.resetSuccess.description=新しいパスワードで金庫の施錠ができます。
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
recover.invalidSelection.title=無効な選択
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=
##Dialog Title
## OnBoarding
recover.onBoarding.useRecoveryKey=回復キーを利用
recover.onBoarding.usePassword=パスワードを使用
recover.onBoarding.pleaseConfirm=続行する前に、以下を確認してください:
recover.onBoarding.intro.ensure=全てのファイルは完全に同期されています。
recover.onBoarding.affirmation=私はこれらの要件を読み、理解しました
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=金庫を変換
+21 -2
View File
@@ -394,8 +394,6 @@ main.vaultlist.contextMenu.unlockNow=지금 잠금 해제
main.vaultlist.contextMenu.vaultoptions=Vault 옵션 보기
main.vaultlist.contextMenu.reveal=드라이브 표시
main.vaultlist.contextMenu.share=공유하기…
main.vaultlist.addVaultBtn.menuItemNew=새 Vault 생성...
main.vaultlist.addVaultBtn.menuItemExisting=기존 Vault 열기...
main.vaultlist.showEventsButton.tooltip=이벤트 뷰어 열기
##Notificaition
main.notification.updateAvailable=업데이트가 있습니다.
@@ -433,6 +431,7 @@ main.vaultDetail.missing.info=Cryptomator가 이 경로에 있는 Vault를 찾
main.vaultDetail.missing.recheck=다시 시도
main.vaultDetail.missing.remove=Vault 목록에서 제거...
main.vaultDetail.missing.changeLocation=Vault 위치 변경
### Missing Vault Config
### Needs Migration
main.vaultDetail.migrateButton=Vault 업그레이드
main.vaultDetail.migratePrompt=Vault에 접근하기 전, 새로운 포맷으로 업그레이드가 필요합니다.
@@ -512,6 +511,26 @@ recoveryKey.recover.resetBtn=초기화
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=비밀번호 재설정 성공
recoveryKey.recover.resetSuccess.description=이제 해당 vault를 새 비밀번호로 잠금 해제할 수 있습니다.
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub Vault
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
convertVault.title=Vault 변환
+57 -2
View File
@@ -96,6 +96,7 @@ addvault.new.readme.accessLocation.4=Šo datni var droši noņemt.
## Existing
addvaultwizard.existing.title=Pievienot esošu glabātavu
addvaultwizard.existing.instruction=Jāizvēlas esošas glabātavas datne "vault.cryptomator". Ja pastāv tikai datne ar nosaukumu "masterkey.cryptomator", tad jāatlasā tā.
addvaultwizard.existing.restore=Atjaunot…
addvaultwizard.existing.chooseBtn=Izvēlēties…
addvaultwizard.existing.filePickerTitle=Atlasīt glabātavas datni
addvaultwizard.existing.filePickerMimeDesc=Cryptomator glabātava
@@ -127,6 +128,7 @@ unlock.unlockBtn=Atslēgt
## Select
unlock.chooseMasterkey.message=Galvenās atslēgas datne nav atrasta
unlock.chooseMasterkey.description=Cryptomator nevarēja atrast galvenās atslēgas datni glabātavai "%s". Lūgums pašrocīgi izvēlēties atslēgas datni.
unlock.chooseMasterkey.restoreInstead=Tā vietā atjaunot galvenās atslēgas datni
unlock.chooseMasterkey.filePickerTitle=Atlasīt galvenās atslēgas datni
unlock.chooseMasterkey.filePickerMimeDesc=Cryptomator galvenā atslēga
## Success
@@ -394,8 +396,9 @@ main.vaultlist.contextMenu.unlockNow=Atslēgt tagad
main.vaultlist.contextMenu.vaultoptions=Rādīt glabātavas iespējas
main.vaultlist.contextMenu.reveal=Atklāt disku
main.vaultlist.contextMenu.share=Kopīgot…
main.vaultlist.addVaultBtn.menuItemNew=Izveidot jaunu glabātavu...
main.vaultlist.addVaultBtn.menuItemExisting=Atvērt esošu glabātavu...
main.vaultlist.addVaultBtn.menuItemNew=Izveidot jaunu glabātavu
main.vaultlist.addVaultBtn.menuItemExisting=Atvērt esošu glabātavu
main.vaultlist.addVaultBtn.menuItemRecover=Atjaunot esošu glabātavu…
main.vaultlist.showEventsButton.tooltip=Atvērt notikumu skatu
##Notificaition
main.notification.updateAvailable=Ir pieejams atjauninājums.
@@ -433,6 +436,9 @@ main.vaultDetail.missing.info=Cryptomator šajā ceļā nevarēja atrast glabāt
main.vaultDetail.missing.recheck=Pārbaudīt atkārtoti
main.vaultDetail.missing.remove=Noņemt no glabātavu saraksta…
main.vaultDetail.missing.changeLocation=Mainīt glabātavas atrašanās vietu…
### Missing Vault Config
main.vaultDetail.missingVaultConfig.info=Trūkst glabātavas konfigurācijas.
main.vaultDetail.missingVaultConfig.restore=Atjaunot glabātavas konfigurāciju
### Needs Migration
main.vaultDetail.migrateButton=Jaunināt glabātavu
main.vaultDetail.migratePrompt=Glabātavu ir nepieciešams jaunināt uz jaunu veidolu, pirms tai varēs piekļūt
@@ -497,6 +503,7 @@ vaultOptions.hub.convertBtn=Pārveidot par uz paroli balstītu glabātavu
recoveryKey.display.title=Parādīt atkopes atslēgu
recoveryKey.create.message=Nepieciešama parole
recoveryKey.create.description=Jāievada "%s" parole, lai parādītu tās atkopes atslēgu.
recoveryKey.recover.description=Jāievada “%s” parole, lai atkoptu glabātavas konfigurāciju.
recoveryKey.display.description=Zemāk esošā atkopes atslēga var tikt izmantota, lai atjaunotu piekļuvi "%s":
recoveryKey.display.StorageHints=Tā ir jātur ļoti drošā vietā, piemēram:\n • jāglabā paroļu pārvaldniekā;\n • jāsaglabā USB zibatmiņā;\n • jāizdrukā uz papīra.
## Reset Password
@@ -509,9 +516,57 @@ recoveryKey.recover.invalidKey=Šī atkopes atslēga nav derīga
recoveryKey.printout.heading=Cryptomator atkopes atslēga\n"%s" \n
### Reset Password
recoveryKey.recover.resetBtn=Atiestatīt
recoveryKey.recover.recoverBtn=Atkopt
### Recovery Key Password Reset Success
recoveryKey.recover.resetSuccess.message=Paroles atiestatīšana sekmīga
recoveryKey.recover.resetSuccess.description=Savu glabātavu var atslēgt ar jauno paroli.
### Recovery Key Vault Config Reset Success
recoveryKey.recover.resetMasterkeyFileSuccess.description=Savu glabātavu tagad var atslēgt ar jauno paroli.
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
recover.existing.title=Glabātava pievienota
recover.existing.message=Glabātava tika sekmīgi pievienota
recover.existing.description=Glabātava “%s” tika pievienota glabātavu sarakstam. Atkope nebija nepieciešama.
##Vault Already Exists - Dialog
recover.alreadyExists.title=Glabātava jau pastāv
recover.alreadyExists.message=Šī glabātava jau ir pievienota
recover.alreadyExists.description=Glabātava “%s” jau ir glabātavu sarakstā, tādējādi tā netika pievienota vēlreiz.
##Invalid Selection - Dialog
recover.invalidSelection.title=Nederīga atlase
recover.invalidSelection.message=Atlasītais nav glabātava
recover.invalidSelection.description=Atlasītajai mapei jābūt derīgai Cryptomator glabātavai.
## Contact Hub Vault Owner - Dialog
contactHubVaultOwner.title=Hub glabātava
contactHubVaultOwner.message=Šī glabātava tika izveidota ar Cryptomator Hub
contactHubVaultOwner.description=Lūgums vērsties pie glabātavas īpašnieka, lai atjaunotu trūkstošo datni. Glabātavas sagatavi var lejupielādēt no Cryptomator Hub.
##Dialog Title
recover.recoverVaultConfig.title=Atkopt glabātavas konfigurāciju
recover.recoverMasterkey.title=Atkopt galveno atslēgu
## OnBoarding
recover.onBoarding.chooseMethod=Jāizvēlas atkopes veids:
recover.onBoarding.useRecoveryKey=Izmantot atkopes atslēgu
recover.onBoarding.usePassword=Izmantot paroli
recover.onBoarding.intro=Jāpārliecinās, ka ir pārbaudīts šis:
recover.onBoarding.pleaseConfirm=Pirms turpināšanas lūgums apstiprināt, ka:
recover.onBoarding.otherwisePleaseConfirm=Pretējā gadījumā lūgums apstiprināt, ka:
recover.onBoarding.allMissing.intro=Ja šo glabātavu pārvalda Cryptomator Hub, tā ir jāatjauno glabātavas īpašniekam.
recover.onBoarding.intro.ensure=Visas datnes ir pilnībā sinhronizētas.
recover.onBoarding.affirmation=Es izlasīju un saprotu šīs prasības
###Vault Config Missing
recover.onBoarding.intro.recoveryKey=Tev ir atkopes atslēga, un Tu zini, vai tika izmantoti lietpratēju iestatījumi.
recover.onBoarding.intro.password=Tev ir glabātavas atslēga, un Tu zini, vai tika izmantoti lietpratēju iestatījumi.
###Masterkey Missing
recover.onBoarding.intro.masterkey.recoveryKey=Tev ir glabātavas atkopes atslēga.
## Expert Settings
recover.expertSettings.shorteningThreshold.title=Šai vērtībai ir jāatbilst tai, kas tika izmantota pirms atkopes, lai nodrošinatu saderību ar iepriekš šifrētajiem datiem.
# Convert Vault
convertVault.title=Pārveidot glabātavu
@@ -135,6 +135,7 @@ main.vaultlist.contextMenu.lock=Заклучи
### Unlocked
main.vaultDetail.lockBtn=Заклучи
### Missing
### Missing Vault Config
### Needs Migration
### Error
@@ -155,6 +156,25 @@ vaultOptions.mount.mountPoint.directoryPickerButton=Избор…
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault
@@ -87,6 +87,7 @@
### Locked
### Unlocked
### Missing
### Missing Vault Config
### Needs Migration
### Error
@@ -105,6 +106,25 @@
### Enter Recovery Key
### Reset Password
### Recovery Key Password Reset Success
### Recovery Key Vault Config Reset Success
# Recover Vault Config File and/or Masterkey
##Add Existing Vault without recovery - Dialog
##Vault Already Exists - Dialog
##Invalid Selection - Dialog
## Contact Hub Vault Owner - Dialog
##Dialog Title
## OnBoarding
###Vault Config Missing
###Masterkey Missing
## Expert Settings
# Convert Vault

Some files were not shown because too many files have changed in this diff Show More