diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index b1a426cfd..3f56c90ac 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -37,16 +37,16 @@ jobs: include: - os: ubuntu-latest appimage-suffix: x86_64 - openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-x64_bin-jmods.zip' - openjfx-sha: '425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c' + openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-x64_bin-jmods.zip' + openjfx-sha: '96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e' - os: ubuntu-24.04-arm appimage-suffix: aarch64 - openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-aarch64_bin-jmods.zip' - openjfx-sha: '7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b' + openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_linux-aarch64_bin-jmods.zip' + openjfx-sha: '951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -75,7 +75,7 @@ jobs: - name: Set version run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} - name: Run maven - run: mvn -B clean package -Plinux -DskipTests -Djavafx.platform=linux + run: mvn -B clean package -Plinux -DskipTests - name: Patch target dir run: | cp LICENSE.txt target @@ -95,7 +95,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler --strip-native-commands --no-header-files --no-man-pages @@ -117,7 +117,6 @@ jobs: --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" - --java-options "--sun-misc-unsafe-memory-access=allow" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" @@ -176,7 +175,7 @@ jobs: gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator-*.AppImage.zsync - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: appimage-${{ matrix.appimage-suffix }} path: | @@ -186,7 +185,7 @@ jobs: if-no-files-found: error - name: Publish AppImage on GitHub Releases if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} @@ -199,10 +198,10 @@ jobs: name: Create PR for aur-bin repo needs: [build, get-version] runs-on: ubuntu-latest - if: github.event_name == 'release' + if: github.event_name == 'release' && needs.get-version.outputs.versionType == 'stable' steps: - name: Download AppImages - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: path: downloads/ merge-multiple: true @@ -213,7 +212,7 @@ jobs: echo "x64-sha256sum=${X64_SHA256}" >> "$GITHUB_OUTPUT" AARCH64_SHA256=$(sha256sum downloads/cryptomator-*-aarch64.AppImage | cut -d ' ' -f1) echo "aarch64-sha256sum=${AARCH64_SHA256}" >> "$GITHUB_OUTPUT" - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: 'cryptomator/aur-bin' token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} @@ -248,7 +247,7 @@ jobs: env: GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} - name: Slack Notification - uses: rtCamp/action-slack-notify@v2 + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: 'Cryptobot' diff --git a/.github/workflows/aur.yml b/.github/workflows/aur.yml index f4f68f63f..b028273a4 100644 --- a/.github/workflows/aur.yml +++ b/.github/workflows/aur.yml @@ -19,6 +19,8 @@ jobs: runs-on: ubuntu-latest needs: [get-version] if: github.event_name == 'workflow_dispatch' || needs.get-version.outputs.versionType == 'stable' + env: + INPUT_TAG: ${{ inputs.tag }} outputs: url: ${{ steps.url.outputs.url}} sha256: ${{ steps.sha256.outputs.sha256}} @@ -27,8 +29,8 @@ jobs: id: url run: | URL=""; - if [[ -n "${{ inputs.tag }}" ]]; then - URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz" + if [[ -n "${INPUT_TAG}" ]]; then + URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${INPUT_TAG}.tar.gz" else URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz" fi @@ -46,7 +48,7 @@ jobs: env: AUR_PR_URL: tbd steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: 'cryptomator/aur' token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} @@ -79,8 +81,8 @@ jobs: env: GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} - name: Slack Notification - uses: rtCamp/action-slack-notify@v2 if: github.event_name == 'release' + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: 'Cryptobot' diff --git a/.github/workflows/av-whitelist.yml b/.github/workflows/av-whitelist.yml index edd5dad30..ca74a3281 100644 --- a/.github/workflows/av-whitelist.yml +++ b/.github/workflows/av-whitelist.yml @@ -30,16 +30,18 @@ jobs: runs-on: ubuntu-latest outputs: fileName: ${{ steps.extractName.outputs.fileName}} + env: + INPUT_URL: ${{ inputs.url }} steps: - name: Extract file name id: extractName run: | - url="${{ inputs.url }}" + url="${INPUT_URL}" echo "fileName=${url##*/}" >> $GITHUB_OUTPUT - name: Download file - run: curl --remote-name ${{ inputs.url }} -L -o ${{steps.extractName.outputs.fileName}} + run: curl --remote-name ${INPUT_URL} -L -o ${{steps.extractName.outputs.fileName}} - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ steps.extractName.outputs.fileName }} path: ${{ steps.extractName.outputs.fileName }} @@ -51,12 +53,12 @@ jobs: if: github.event_name == 'workflow_call' || inputs.kaspersky steps: - name: Download artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: ${{ needs.download-file.outputs.fileName }} path: upload - name: Upload to Kaspersky - uses: SamKirkland/FTP-Deploy-Action@v4.3.6 + uses: SamKirkland/FTP-Deploy-Action@a51268f67f6605236975928ae28b0f7e9971d50a # v4.6.3 with: protocol: ftps server: allowlist.kaspersky-labs.com @@ -71,12 +73,12 @@ jobs: if: github.event_name == 'workflow_call' || inputs.avast steps: - name: Download artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: ${{ needs.download-file.outputs.fileName }} path: upload - - name: Upload to Avast - uses: wlixcc/SFTP-Deploy-Action@v1.2.6 + - name: Upload to Avast + uses: wlixcc/SFTP-Deploy-Action@a5ccb9c6211a94cc59404f0fdb2a9936a6dfee64 # v1.2.6 with: server: whitelisting.avast.com port: 22 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ded79b7d..c9a0ea0c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,14 +22,14 @@ jobs: name: Compile and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-java@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} cache: 'maven' - name: Cache SonarCloud packages - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar @@ -37,7 +37,7 @@ jobs: - name: Build and Test run: > xvfb-run - mvn -B verify -Djavafx.platform=linux + mvn -B verify jacoco:report org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Pcoverage @@ -49,7 +49,7 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - name: Draft a release if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: draft: true discussion_category_name: releases diff --git a/.github/workflows/check-jdk-updates.yml b/.github/workflows/check-jdk-updates.yml index d926f6630..4e9633aac 100644 --- a/.github/workflows/check-jdk-updates.yml +++ b/.github/workflows/check-jdk-updates.yml @@ -26,7 +26,7 @@ jobs: run: echo 'JDK_MAJOR_VERSION=${{ env.JDK_VERSION }}'.substring(0,20) >> "$env:GITHUB_ENV" shell: pwsh - name: Checkout latest JDK ${{ env.JDK_MAJOR_VERSION }} - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: java-version: ${{ env.JDK_MAJOR_VERSION}} distribution: ${{ env.JDK_VENDOR }} @@ -70,7 +70,7 @@ jobs: } - name: Notify if: steps.determine.outputs.UPDATE_AVAILABLE == 'true' - uses: rtCamp/action-slack-notify@v2 + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: 'Cryptobot' diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 810da7161..f4ae6b439 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -25,11 +25,11 @@ env: JAVA_DIST: 'temurin' JAVA_VERSION: '25.0.0' COFFEELIBS_JDK: 25 - COFFEELIBS_JDK_VERSION: '25.0.0+36-0ppa3' #TODO: update coffeelibs - OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-x64_bin-jmods.zip' - OPENJFX_JMODS_AMD64_HASH: '425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c' - OPENJFX_JMODS_AARCH64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_linux-aarch64_bin-jmods.zip' - OPENJFX_JMODS_AARCH64_HASH: '7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b' + COFFEELIBS_JDK_VERSION: '25.0.0+36-0ppa1' + 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' jobs: get-version: @@ -41,13 +41,15 @@ jobs: name: Build Debian Package runs-on: ubuntu-22.04 needs: [get-version] + env: + INPUT_PPAVER: ${{ inputs.ppaver }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - id: deb-version name: Determine deb-version run: | - if [ -n "${{inputs.ppaver}}" ]; then - echo "debVersion=${{inputs.ppaver }}" >> "$GITHUB_OUTPUT" + if [ -n "${INPUT_PPAVER}" ]; then + echo "debVersion=${INPUT_PPAVER}" >> "$GITHUB_OUTPUT" else echo "debVersion=${{needs.get-version.outputs.semVerStr}}" >> "$GITHUB_OUTPUT" fi @@ -57,14 +59,14 @@ jobs: sudo apt-get update sudo apt-get install debhelper devscripts dput coffeelibs-jdk-${{ env.COFFEELIBS_JDK }}=${{ env.COFFEELIBS_JDK_VERSION }} - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} check-latest: true cache: 'maven' - name: Run maven - run: mvn -B clean package -Plinux -Djavafx.platform=linux -DskipTests + run: mvn -B clean package -Plinux -DskipTests - name: Download OpenJFX jmods id: download-jmods run: | @@ -140,7 +142,7 @@ jobs: run: | gpg --batch --quiet --passphrase-fd 0 --pinentry-mode loopback -u 615D449FE6E6A235 --detach-sign -a cryptomator_*_amd64.deb - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: linux-deb-package path: | diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 6db100254..22d2a5fc5 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -7,12 +7,13 @@ on: jobs: check-dependencies: - uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@v1 + uses: skymatic/workflows/.github/workflows/run-dependency-check.yml@1074588008ae3326a2221ea451783280518f0366 # v3.0.1 with: runner-os: 'ubuntu-latest' java-distribution: 'temurin' java-version: 25 - check-command: 'mvn -B validate -Pdependency-check -Djavafx.platform=linux' secrets: nvd-api-key: ${{ secrets.NVD_API_KEY }} + ossindex-username: ${{ secrets.OSSINDEX_USERNAME }} + ossindex-token: ${{ secrets.OSSINDEX_API_TOKEN }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/dl-stats.yml b/.github/workflows/dl-stats.yml index 44dd95d4f..401fa010a 100644 --- a/.github/workflows/dl-stats.yml +++ b/.github/workflows/dl-stats.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Get download count of latest releases id: get-stats - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const query = `query($owner:String!, $name:String!) { @@ -53,7 +53,7 @@ jobs: INTERVAL: 900 JSON_DATA: ${{ steps.get-stats.outputs.result }} - name: Upload Results - uses: fjogeleit/http-request-action@v1 + uses: fjogeleit/http-request-action@1297c6fc63a79b147d1676540a3fd9d2e37817c5 # v1.16.5 with: url: 'https://graphite-us-central1.grafana.net/metrics' method: 'POST' diff --git a/.github/workflows/error-db.yml b/.github/workflows/error-db.yml index fd4022a73..5c2ffebe8 100644 --- a/.github/workflows/error-db.yml +++ b/.github/workflows/error-db.yml @@ -14,7 +14,7 @@ jobs: - name: Query Discussion Data if: github.event_name == 'discussion_comment' || github.event_name == 'discussion' && github.event.action != 'deleted' id: query-data - uses: actions/github-script@v8 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const query = `query ($owner: String!, $name: String!, $discussionNumber: Int!) { @@ -42,7 +42,7 @@ jobs: return await github.graphql(query, variables) - name: Get Gist id: get-gist - uses: andymckay/get-gist-action@master + uses: andymckay/get-gist-action@cf3bc8164af24126f7e5979eb6d3dc0c12309bd1 # not_tagged with: gistURL: https://gist.github.com/cryptobot/accba9fb9555e7192271b85606f97230 - name: Merge Error Code Data @@ -58,7 +58,7 @@ jobs: env: DISCUSSION: ${{ steps.query-data.outputs.result }} - name: Patch Gist - uses: exuanbo/actions-deploy-gist@v1 + uses: exuanbo/actions-deploy-gist@47697fceaeea2006a90594ee24eb9cd0a1121ef8 # v1.1.4 with: token: ${{ secrets.CRYPTOBOT_GIST_TOKEN }} gist_id: accba9fb9555e7192271b85606f97230 diff --git a/.github/workflows/flathub.yml b/.github/workflows/flathub.yml index 2dbfff0f5..d233a747b 100644 --- a/.github/workflows/flathub.yml +++ b/.github/workflows/flathub.yml @@ -26,13 +26,10 @@ jobs: - name: Determine tarball url id: url run: | - URL=""; - if [[ -n "${{ inputs.tag }}" ]]; then - URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ inputs.tag }}.tar.gz" - else - URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz" - fi + URL="https://github.com/cryptomator/cryptomator/archive/refs/tags/${TAG}.tar.gz" echo "url=${URL}" >> "$GITHUB_OUTPUT" + env: + TAG: ${{ inputs.tag || github.event.release.tag_name}} - name: Download source tarball and compute checksum id: sha512 run: | @@ -46,7 +43,7 @@ jobs: env: FLATHUB_PR_URL: tbd steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: repository: 'flathub/org.cryptomator.Cryptomator' token: ${{ secrets.CRYPTOBOT_PR_TOKEN }} @@ -74,7 +71,7 @@ jobs: env: GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} - name: Slack Notification - uses: rtCamp/action-slack-notify@v2 + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 if: github.event_name == 'release' env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 7fc1c09b5..b1c728fa8 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -35,11 +35,11 @@ jobs: revNum: ${{ steps.versions.outputs.revNum }} type: ${{ steps.versions.outputs.type}} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -49,8 +49,8 @@ jobs: run: | if [[ $GITHUB_REF =~ refs/tags/[0-9]+\.[0-9]+\.[0-9]+.* ]]; then SEM_VER_STR=${GITHUB_REF##*/} - elif [[ "${{ inputs.version }}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then - SEM_VER_STR="${{ inputs.version }}" + elif [[ "${VERSION_STRING}" =~ [0-9]+\.[0-9]+\.[0-9]+.* ]]; then + SEM_VER_STR="${VERSION_STRING}" else SEM_VER_STR=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` fi @@ -70,7 +70,9 @@ jobs: echo "semVerNum=${SEM_VER_NUM}" >> $GITHUB_OUTPUT echo "revNum=${REVCOUNT}" >> $GITHUB_OUTPUT echo "type=${TYPE}" >> $GITHUB_OUTPUT + env: + VERSION_STRING: ${{ inputs.version }} - name: Validate Version - uses: skymatic/semver-validation-action@v3 + uses: skymatic/semver-validation-action@7a6ae1c9e121540d11c9c7e4e667c83d583aa153 # v3.0.0 with: version: ${{ steps.versions.outputs.semVerStr }} \ No newline at end of file diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml index c54a25a50..b0af671a6 100644 --- a/.github/workflows/mac-dmg-x64.yml +++ b/.github/workflows/mac-dmg-x64.yml @@ -44,12 +44,12 @@ jobs: architecture: x64 output-suffix: x64 fuse-lib: macFUSE - openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_osx-x64_bin-jmods.zip' - openjfx-sha: '6e62a426d43c168a488521f904a523f3dd6ee2cf103e08136f2fd465c828a105' + openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-x64_bin-jmods.zip' + openjfx-sha: '0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -79,7 +79,7 @@ jobs: - name: Set version run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} - name: Run maven - run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests + run: mvn -B clean package -Pmac -DskipTests - name: Patch target dir run: | cp LICENSE.txt target @@ -99,7 +99,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages @@ -121,7 +121,6 @@ jobs: --app-version "${{ needs.get-version.outputs.semVerNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac" - --java-options "--sun-misc-unsafe-memory-access=allow" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dfile.encoding=\"utf-8\"" @@ -151,9 +150,23 @@ jobs: VERSION_NO: ${{ needs.get-version.outputs.semVerNum }} REVISION_NO: ${{ needs.get-version.outputs.revNum }} PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }} + - name: Build and install DockTilePlugin + env: + DERIVED_DATA_PATH: dist/mac/DockTilePlugin/build + run: | + xcodebuild -project dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj \ + -scheme DockTilePlugin \ + -configuration Release \ + -destination "platform=macOS,arch=x86_64" \ + -derivedDataPath ${DERIVED_DATA_PATH} \ + -quiet \ + clean build + mkdir -p Cryptomator.app/Contents/PlugIns + cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin Cryptomator.app/Contents/PlugIns/ + rm -rf ${DERIVED_DATA_PATH} - name: Generate license for dmg run: > - mvn -B -Djavafx.platform=mac license:add-third-party + mvn -B license:add-third-party -Dlicense.thirdPartyFilename=license.rtf -Dlicense.outputDirectory=dist/mac/dmg/resources -Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl @@ -248,7 +261,7 @@ jobs: CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} - name: Notarize .dmg if: startsWith(github.ref, 'refs/tags/') || inputs.notarize - uses: cocoalibs/xcode-notarization-action@v1 + uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3 with: app-path: 'Cryptomator-*.dmg' apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} @@ -269,7 +282,7 @@ jobs: run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db continue-on-error: true - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dmg-${{ matrix.output-suffix }} path: | @@ -278,7 +291,7 @@ jobs: if-no-files-found: error - name: Publish dmg on GitHub Releases if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index fa47cd405..cc1af8a78 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -42,12 +42,12 @@ jobs: architecture: aarch64 output-suffix: arm64 fuse-lib: FUSE-T - openjfx-url: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_osx-aarch64_bin-jmods.zip' - openjfx-sha: 'b5a94a13077507003fa852512bfa33f4fb680bc8076d8002e4227a84c85171d4' + openjfx-url: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_osx-aarch64_bin-jmods.zip' + openjfx-sha: '13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -77,7 +77,7 @@ jobs: - name: Set version run : mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} - name: Run maven - run: mvn -B -Djavafx.platform=mac clean package -Pmac -DskipTests + run: mvn -B clean package -Pmac -DskipTests - name: Patch target dir run: | cp LICENSE.txt target @@ -97,7 +97,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages @@ -119,7 +119,6 @@ jobs: --app-version "${{ needs.get-version.outputs.semVerNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.mac" - --java-options "--sun-misc-unsafe-memory-access=allow" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dfile.encoding=\"utf-8\"" @@ -150,9 +149,23 @@ jobs: VERSION_NO: ${{ needs.get-version.outputs.semVerNum }} REVISION_NO: ${{ needs.get-version.outputs.revNum }} PROVISIONING_PROFILE_BASE64: ${{ secrets.MACOS_PROVISIONING_PROFILE_BASE64 }} + - name: Build and install DockTilePlugin + env: + DERIVED_DATA_PATH: dist/mac/DockTilePlugin/build + run: | + xcodebuild -project dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj \ + -scheme DockTilePlugin \ + -configuration Release \ + -destination "platform=macOS,arch=arm64" \ + -derivedDataPath ${DERIVED_DATA_PATH} \ + -quiet \ + clean build + mkdir -p Cryptomator.app/Contents/PlugIns + cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin Cryptomator.app/Contents/PlugIns/ + rm -rf ${DERIVED_DATA_PATH} - name: Generate license for dmg run: > - mvn -B -Djavafx.platform=mac license:add-third-party + mvn -B license:add-third-party -Dlicense.thirdPartyFilename=license.rtf -Dlicense.outputDirectory=dist/mac/dmg/resources -Dlicense.fileTemplate=dist/mac/dmg/resources/licenseTemplate.ftl @@ -247,7 +260,7 @@ jobs: CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} - name: Notarize .dmg if: startsWith(github.ref, 'refs/tags/') || inputs.notarize - uses: cocoalibs/xcode-notarization-action@v1 + uses: cocoalibs/xcode-notarization-action@5cf433d494b6fa26504b574c591f4dd120388846 # v1.0.3 with: app-path: 'Cryptomator-*.dmg' apple-id: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} @@ -268,7 +281,7 @@ jobs: run: security delete-keychain $RUNNER_TEMP/codesign.keychain-db continue-on-error: true - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dmg-${{ matrix.output-suffix }} path: | @@ -277,7 +290,7 @@ jobs: if-no-files-found: error - name: Publish dmg on GitHub Releases if: startsWith(github.ref, 'refs/tags/') && github.event.action == 'published' - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml index f35101148..9da0bfbc6 100644 --- a/.github/workflows/no-response.yml +++ b/.github/workflows/no-response.yml @@ -12,7 +12,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v10 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: days-before-stale: 14 days-before-close: 0 diff --git a/.github/workflows/post-publish.yml b/.github/workflows/post-publish.yml index eaa6fb3f4..14b115f02 100644 --- a/.github/workflows/post-publish.yml +++ b/.github/workflows/post-publish.yml @@ -19,14 +19,14 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Publish asc on GitHub Releases - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} files: | cryptomator-*.tar.gz.asc - name: Slack Notification - uses: rtCamp/action-slack-notify@v2 + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: 'Cryptobot' diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 0047fc37e..cfb013c05 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -16,11 +16,11 @@ jobs: name: Compile and Test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-java@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} cache: 'maven' - name: Build and Test - run: xvfb-run mvn -B clean install jacoco:report -Pcoverage -Djavafx.platform=linux \ No newline at end of file + run: xvfb-run mvn -B clean install jacoco:report -Pcoverage \ No newline at end of file diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml index ab0c1c8e8..e6c12a2d1 100644 --- a/.github/workflows/release-check.yml +++ b/.github/workflows/release-check.yml @@ -19,9 +19,9 @@ jobs: name: Validate commits pushed to release/hotfix branch to fulfill release requirements runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ env.JAVA_DIST }} java-version: ${{ env.JAVA_VERSION }} @@ -49,7 +49,7 @@ jobs: exit 1 fi - name: Cache NVD DB - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ~/.m2/repository/org/owasp/dependency-check-data/ key: dependency-check-${{ github.run_id }} @@ -60,6 +60,6 @@ jobs: - name: Run org.owasp:dependency-check plugin id: dependency-check continue-on-error: true - run: mvn -B verify -Pdependency-check -DskipTests -Djavafx.platform=linux + run: mvn -B verify -Pdependency-check -DskipTests env: NVD_API_KEY: ${{ secrets.NVD_API_KEY }} \ No newline at end of file diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index b261d293e..1a2dd28af 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,7 +12,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v10 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: days-before-stale: 365 days-before-close: 90 diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index 0fa4a42c5..6dd12982a 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -26,8 +26,8 @@ on: env: - OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/24.0.1/openjfx-24.0.1_windows-x64_bin-jmods.zip' - OPENJFX_JMODS_AMD64_HASH: 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f' + OPENJFX_JMODS_AMD64: 'https://download2.gluonhq.com/openjfx/25/openjfx-25_windows-x64_bin-jmods.zip' + OPENJFX_JMODS_AMD64_HASH: 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7' WINFSP_MSI: 'https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-2.1.25156.msi' WINFSP_MSI_HASH: '073a70e00f77423e34bed98b86e600def93393ba5822204fac57a29324db9f7a' WINFSP_UNINSTALLER: 'https://github.com/cryptomator/winfsp-uninstaller/releases/latest/download/winfsp-uninstaller.exe' @@ -54,15 +54,10 @@ jobs: java-dist: 'zulu' #TODO: is finally temurin possible? java-version: '25.0.0+36' java-package: 'jdk' - - arch: arm64 - os: windows-11-arm - java-dist: 'liberica' - java-version: '24.0.1+11' #TODO: which distro to use here? - java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ matrix.java-dist }} java-version: ${{ matrix.java-version }} @@ -102,7 +97,7 @@ jobs: - name: Set version run: mvn versions:set -DnewVersion=${{ needs.get-version.outputs.semVerStr }} - name: Run maven - run: mvn -B clean package -Pwin -DskipTests -Djavafx.platform=win + run: mvn -B clean package -Pwin -DskipTests - name: Patch target dir run: | cp LICENSE.txt target @@ -122,7 +117,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages @@ -144,7 +139,6 @@ jobs: --app-version "${{ needs.get-version.outputs.semVerNum }}.${{ needs.get-version.outputs.revNum }}" --java-options "--enable-preview" --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.win,org.cryptomator.integrations.win" - --java-options "--sun-misc-unsafe-memory-access=allow" --java-options "-Xss5m" --java-options "-Xmx256m" --java-options "-Dcryptomator.appVersion=\"${{ needs.get-version.outputs.semVerStr }}\"" @@ -156,7 +150,7 @@ jobs: --java-options "-Dcryptomator.p12Path=\"@{appdata}/Cryptomator/key.p12;@{userhome}/AppData/Roaming/Cryptomator/key.p12\"" --java-options "-Dcryptomator.ipcSocketPath=\"@{localappdata}/Cryptomator/ipc.socket\"" --java-options "-Dcryptomator.mountPointsDir=\"@{userhome}/Cryptomator\"" - --java-options "-Dcryptomator.loopbackAlias=\"${{ env.LOOPBACK_ALIAS }}\"" + --java-options "-Dcryptomator.loopbackAlias=\"cryptomator-vault\"" --java-options "-Dcryptomator.showTrayIcon=true" --java-options "-Dcryptomator.buildNumber=\"msi-${{ needs.get-version.outputs.revNum }}\"" --java-options "-Dcryptomator.integrationsWin.autoStartShellLinkName=\"Cryptomator\"" @@ -200,7 +194,7 @@ jobs: Get-ChildItem -Recurse -Path "jpackage-jmod" -File wixhelper.dll | Select-Object -Last 1 | Copy-Item -Destination "appdir" - name: Sign DLLs with Actalis CodeSigner if: inputs.sign || github.event_name == 'release' - uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f + uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version with: base-dir: 'appdir' file-extensions: 'dll,exe,ps1' @@ -225,7 +219,7 @@ jobs: } - name: Generate license for MSI run: > - mvn -B license:add-third-party "-Djavafx.platform=win" + mvn -B license:add-third-party "-Dlicense.thirdPartyFilename=license.rtf" "-Dlicense.outputDirectory=dist/win/resources" "-Dlicense.fileTemplate=dist/win/resources/licenseTemplate.ftl" @@ -259,7 +253,7 @@ jobs: JP_WIXHELPER_DIR: ${{ github.workspace }}\appdir - name: Sign msi with Actalis CodeSigner if: inputs.sign || github.event_name == 'release' - uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f + uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version with: base-dir: 'installer' file-extensions: 'msi' @@ -277,7 +271,7 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: msi-${{ matrix.arch }} path: | @@ -298,28 +292,22 @@ jobs: java-dist: 'zulu' java-version: '24.0.1+9' java-package: 'jdk' - - arch: arm64 - os: windows-11-arm - executable-suffix: arm64 - java-dist: 'liberica' - java-version: '24.0.1+11' - java-package: 'jdk+fx' #This is needed, as liberica contains JFX 24 Jmods for Windows ARM64 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install wix and extensions run: | dotnet tool install --global wix --version 6.0.0 wix.exe extension add WixToolset.BootstrapperApplications.wixext/6.0.0 --global wix.exe extension add WixToolset.Util.wixext/6.0.0 --global - name: Download .msi - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: name: msi-${{ matrix.arch }} path: dist/win/bundle/resources - name: Strip version info from msi file name run: mv dist/win/bundle/resources/Cryptomator*.msi dist/win/bundle/resources/Cryptomator.msi - name: Setup Java - uses: actions/setup-java@v5 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: ${{ matrix.java-dist }} java-version: ${{ matrix.java-version }} @@ -328,7 +316,7 @@ jobs: cache: 'maven' - name: Generate license for exe run: > - mvn -B license:add-third-party "-Djavafx.platform=win" + mvn -B license:add-third-party "-Dlicense.thirdPartyFilename=license.rtf" "-Dlicense.fileTemplate=dist/win/bundle/resources/licenseTemplate.ftl" "-Dlicense.outputDirectory=dist/win/bundle/resources" @@ -371,7 +359,7 @@ jobs: wix burn detach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe - name: Sign burn engine with Actalis CodeSigner if: inputs.sign || github.event_name == 'release' - uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f + uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version with: base-dir: 'tmp' file-extensions: 'exe' @@ -384,7 +372,7 @@ jobs: wix burn reattach installer/unsigned/Cryptomator-Installer.exe -engine tmp/engine.exe -o installer/Cryptomator-Installer.exe - name: Sign installer with Actalis CodeSigner if: inputs.sign || github.event_name == 'release' - uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f + uses: skymatic/workflows/.github/actions/win-sign-action@450e322ff2214d0be0b079b63343c894f3ef735f # no specific version with: base-dir: 'installer' file-extensions: 'exe' @@ -402,7 +390,7 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: exe-${{ matrix.executable-suffix }} path: | @@ -417,26 +405,22 @@ jobs: needs: [ build-msi, build-exe ] outputs: download-url-msi-x64: ${{ fromJSON(steps.publish.outputs.assets)[0].browser_download_url }} - download-url-msi-arm64: ${{ fromJSON(steps.publish.outputs.assets)[1].browser_download_url }} download-url-exe-x64: ${{ fromJSON(steps.publish.outputs.assets)[2].browser_download_url }} - download-url-exe-arm64: ${{ fromJSON(steps.publish.outputs.assets)[3].browser_download_url }} steps: - name: Download installers - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: merge-multiple: true - name: Publish installers on GitHub Releases id: publish - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1 with: fail_on_unmatched_files: true token: ${{ secrets.CRYPTOBOT_RELEASE_TOKEN }} # do not change ordering of filelist, required for correct job output files: | *x64.msi - *arm64.msi *x64.exe - *arm64.exe *.asc allowlist-msi-x64: @@ -446,13 +430,6 @@ jobs: url: ${{ needs.publish.outputs.download-url-msi-x64 }} secrets: inherit - allowlist-msi-arm64: - uses: ./.github/workflows/av-whitelist.yml - needs: [ publish ] - with: - url: ${{ needs.publish.outputs.download-url-msi-arm64 }} - secrets: inherit - allowlist-exe-x64: uses: ./.github/workflows/av-whitelist.yml needs: [ publish, allowlist-msi-x64 ] @@ -460,13 +437,6 @@ jobs: url: ${{ needs.publish.outputs.download-url-exe-x64 }} secrets: inherit - allowlist-exe-arm64: - uses: ./.github/workflows/av-whitelist.yml - needs: [ publish, allowlist-msi-arm64 ] - with: - url: ${{ needs.publish.outputs.download-url-exe-arm64 }} - secrets: inherit - notify-winget: name: Notify for winget-release if: needs.get-version.outputs.versionType == 'stable' @@ -474,7 +444,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Slack Notification - uses: rtCamp/action-slack-notify@v2 + uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: 'Cryptobot' diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 0beb75544..5ab150229 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -18,7 +18,7 @@ jobs: env: GH_TOKEN: ${{ secrets.CRYPTOBOT_PR_TOKEN }} - name: Submit package - uses: vedantmgoyal2009/winget-releaser@main + uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # no_specific_version with: identifier: Cryptomator.Cryptomator version: ${{ inputs.tag }} diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index f5edd2df5..947fe56c4 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -19,16 +19,16 @@ if [[ ! "${CPU_ARCH}" =~ x86_64|aarch64 ]]; then echo "Platform ${CPU_ARCH} not mvn -f ../../../pom.xml versions:set -DnewVersion=${SEMVER_STR} # compile -mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests -Djavafx.platform=linux +mvn -B -f ../../../pom.xml clean package -Plinux -DskipTests cp ../../../LICENSE.txt ../../../target cp ../../../target/cryptomator-*.jar ../../../target/mods -JAVAFX_VERSION=24.0.1 +JAVAFX_VERSION=25 JAVAFX_ARCH="x64" -JAVAFX_JMODS_SHA256='425fac742b9fbd095b2ce868cff82d1024620f747c94a7144d0a4879e756146c' +JAVAFX_JMODS_SHA256='96e520f48610d8ffb94ca30face1f11ffe8a977ddc1c4ff80b1a9e9f048bd94e' if [ "${CPU_ARCH}" = "aarch64" ]; then JAVAFX_ARCH="aarch64" - JAVAFX_JMODS_SHA256='7e02edd0f4ee5527a27c94b0bbba66fcaaff41009119e45d0eca0f96ddfb6e7b' + JAVAFX_JMODS_SHA256='951c52481af0ec5885b06f1ebaa8a10da7e8ea23c5e1ef3e2f6f11fa1b3a7ce1' fi # download javaFX jmods @@ -62,7 +62,7 @@ ${JAVA_HOME}/bin/jlink \ --verbose \ --output runtime \ --module-path "${JMOD_PATHS}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/linux/debian/control b/dist/linux/debian/control index 009d73ff5..c525fa019 100644 --- a/dist/linux/debian/control +++ b/dist/linux/debian/control @@ -2,7 +2,7 @@ Source: cryptomator Maintainer: Cryptobot Section: utils Priority: optional -Build-Depends: debhelper (>=10), coffeelibs-jdk-25 (>= 24.0.1+9-0ppa3), libgtk-3-0, libxxf86vm1, libgl1 +Build-Depends: debhelper (>=10), coffeelibs-jdk-25 (>= 25.0.0+36-0ppa1), 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 @@ -12,7 +12,7 @@ Package: cryptomator Architecture: any Section: utils Priority: optional -Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3 +Depends: ${shlibs:Depends}, ${misc:Depends}, fuse3, libgtk-3-0 (>= 3.20.0) Recommends: gvfs-backends, gvfs-fuse, gnome-keyring XB-AppName: Cryptomator XB-Category: Utility;Security;FileTools; diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index d4edb1f79..0033d048f 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -28,7 +28,7 @@ override_dh_auto_build: $(JAVA_HOME)/bin/jlink \ --output runtime \ --module-path "${JMODS_PATH}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ @@ -45,7 +45,6 @@ override_dh_auto_build: --vendor "Skymatic GmbH" \ --java-options "--enable-preview" \ --java-options "--enable-native-access=javafx.graphics,org.cryptomator.jfuse.linux.amd64,org.cryptomator.jfuse.linux.aarch64,org.purejava.appindicator" \ - --java-options "--sun-misc-unsafe-memory-access=allow" \ --copyright "(C) 2016 - 2025 Skymatic GmbH" \ --java-options "-Xss5m" \ --java-options "-Xmx256m" \ diff --git a/dist/mac/.gitignore b/dist/mac/.gitignore index bd6569978..81a0afdc2 100644 --- a/dist/mac/.gitignore +++ b/dist/mac/.gitignore @@ -1 +1,2 @@ embedded.provisionprofile +xcuserdata/ diff --git a/dist/mac/DockTilePlugin/CryptomatorDockTilePlugin.swift b/dist/mac/DockTilePlugin/CryptomatorDockTilePlugin.swift new file mode 100644 index 000000000..1b54ea17d --- /dev/null +++ b/dist/mac/DockTilePlugin/CryptomatorDockTilePlugin.swift @@ -0,0 +1,19 @@ +// +// CryptomatorDockTilePlugin.swift +// Integrations +// +// Created by Tobias Hagemann on 22.09.25. +// Copyright © 2025 Cryptomator. All rights reserved. +// + +import AppKit + +class CryptomatorDockTilePlugin: NSObject, NSDockTilePlugIn { + func setDockTile(_ dockTile: NSDockTile?) { + guard let dockTile = dockTile, let image = Bundle(for: Self.self).image(forResource: "Cryptomator") else { + return + } + dockTile.contentView = NSImageView(image: image) + dockTile.display() + } +} diff --git a/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.pbxproj b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.pbxproj new file mode 100644 index 000000000..fa7ec5b77 --- /dev/null +++ b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.pbxproj @@ -0,0 +1,314 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 74E08DE12E8584DE007E665C /* CryptomatorDockTilePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */; }; + 74E08DED2E858532007E665C /* Cryptomator.icns in Resources */ = {isa = PBXBuildFile; fileRef = 74E08DEC2E858532007E665C /* Cryptomator.icns */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Cryptomator.docktileplugin; sourceTree = BUILT_PRODUCTS_DIR; }; + 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptomatorDockTilePlugin.swift; sourceTree = ""; }; + 74E08DEC2E858532007E665C /* Cryptomator.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Cryptomator.icns; path = ../resources/Cryptomator.icns; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 74E08DD62E858467007E665C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 74E08DD02E858467007E665C = { + isa = PBXGroup; + children = ( + 74E08DE02E85847E007E665C /* CryptomatorDockTilePlugin.swift */, + 74E08DEC2E858532007E665C /* Cryptomator.icns */, + 74E08DDA2E858467007E665C /* Products */, + ); + sourceTree = ""; + }; + 74E08DDA2E858467007E665C /* Products */ = { + isa = PBXGroup; + children = ( + 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 74E08DD82E858467007E665C /* DockTilePlugin */ = { + isa = PBXNativeTarget; + buildConfigurationList = 74E08DDD2E858467007E665C /* Build configuration list for PBXNativeTarget "DockTilePlugin" */; + buildPhases = ( + 74E08DD52E858467007E665C /* Sources */, + 74E08DD62E858467007E665C /* Frameworks */, + 74E08DD72E858467007E665C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DockTilePlugin; + packageProductDependencies = ( + ); + productName = DockTilePlugin; + productReference = 74E08DD92E858467007E665C /* Cryptomator.docktileplugin */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 74E08DD12E858467007E665C /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 2600; + ORGANIZATIONNAME = Cryptomator; + TargetAttributes = { + 74E08DD82E858467007E665C = { + CreatedOnToolsVersion = 26.0.1; + }; + }; + }; + buildConfigurationList = 74E08DD42E858467007E665C /* Build configuration list for PBXProject "DockTilePlugin" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 74E08DD02E858467007E665C; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 74E08DDA2E858467007E665C /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 74E08DD82E858467007E665C /* DockTilePlugin */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 74E08DD72E858467007E665C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74E08DED2E858532007E665C /* Cryptomator.icns in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 74E08DD52E858467007E665C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74E08DE12E8584DE007E665C /* CryptomatorDockTilePlugin.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 74E08DDB2E858467007E665C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = YZQJQUHA3L; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.5; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 74E08DDC2E858467007E665C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = YZQJQUHA3L; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.5; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 74E08DDE2E858467007E665C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Cryptomator. All rights reserved."; + INFOPLIST_KEY_NSPrincipalClass = CryptomatorDockTilePlugin; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.DockTilePlugin; + PRODUCT_NAME = Cryptomator; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + WRAPPER_EXTENSION = docktileplugin; + }; + name = Debug; + }; + 74E08DDF2E858467007E665C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2025 Cryptomator. All rights reserved."; + INFOPLIST_KEY_NSPrincipalClass = CryptomatorDockTilePlugin; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = org.cryptomator.DockTilePlugin; + PRODUCT_NAME = Cryptomator; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + WRAPPER_EXTENSION = docktileplugin; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 74E08DD42E858467007E665C /* Build configuration list for PBXProject "DockTilePlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 74E08DDB2E858467007E665C /* Debug */, + 74E08DDC2E858467007E665C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 74E08DDD2E858467007E665C /* Build configuration list for PBXNativeTarget "DockTilePlugin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 74E08DDE2E858467007E665C /* Debug */, + 74E08DDF2E858467007E665C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 74E08DD12E858467007E665C /* Project object */; +} diff --git a/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/xcshareddata/xcschemes/DockTilePlugin.xcscheme b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/xcshareddata/xcschemes/DockTilePlugin.xcscheme new file mode 100644 index 000000000..7d86bdcc5 --- /dev/null +++ b/dist/mac/DockTilePlugin/DockTilePlugin.xcodeproj/xcshareddata/xcschemes/DockTilePlugin.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index bf63505a5..26673589b 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -32,15 +32,15 @@ REVISION_NO=`git rev-list --count HEAD` VERSION_NO=`mvn -f../../../pom.xml help:evaluate -Dexpression=project.version -q -DforceStdout | sed -rn 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p'` FUSE_LIB="FUSE-T" -JAVAFX_VERSION=24.0.1 +JAVAFX_VERSION=25 JAVAFX_ARCH="undefined" JAVAFX_JMODS_SHA256="undefined" if [ "$(machine)" = "arm64e" ]; then JAVAFX_ARCH="aarch64" - JAVAFX_JMODS_SHA256="b5a94a13077507003fa852512bfa33f4fb680bc8076d8002e4227a84c85171d4" + JAVAFX_JMODS_SHA256="13f8c0513c40c95881479fbcf0465a29a60217393fb0656f5e4eab78a9442fba" else JAVAFX_ARCH="x64" - JAVAFX_JMODS_SHA256="6e62a426d43c168a488521f904a523f3dd6ee2cf103e08136f2fd465c828a105" + JAVAFX_JMODS_SHA256="0eba73fb28a24c845175d16fa2f8c081c936ce6de1be9b79eb6119fa32e53d52" fi JAVAFX_JMODS_URL="https://download2.gluonhq.com/openjfx/${JAVAFX_VERSION}/openjfx-${JAVAFX_VERSION}_osx-${JAVAFX_ARCH}_bin-jmods.zip" @@ -71,7 +71,7 @@ if [ "${POM_JFX_VERSION}" -ne "${JMOD_VERSION}" ]; then fi # compile -mvn -B -Djavafx.platform=mac -f../../../pom.xml clean package -DskipTests -Pmac +mvn -B -f../../../pom.xml clean package -DskipTests -Pmac cp ../../../LICENSE.txt ../../../target cp ../../../target/${MAIN_JAR_GLOB} ../../../target/mods @@ -85,7 +85,7 @@ fi ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JMOD_PATHS}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ @@ -133,8 +133,21 @@ sed -i '' "s|###BUNDLE_SHORT_VERSION_STRING###|${VERSION_NO}|g" ${APP_NAME}.app/ sed -i '' "s|###BUNDLE_VERSION###|${REVISION_NO}|g" ${APP_NAME}.app/Contents/Info.plist cp ../embedded.provisionprofile ${APP_NAME}.app/Contents/ +# build and install dock tile plugin +echo "Building and installing Cryptomator.docktileplugin..." +DERIVED_DATA_PATH=../DockTilePlugin/build +xcodebuild -project ../DockTilePlugin/DockTilePlugin.xcodeproj \ + -scheme DockTilePlugin \ + -configuration Release \ + -derivedDataPath ${DERIVED_DATA_PATH} \ + -quiet \ + clean build +mkdir -p ${APP_NAME}.app/Contents/PlugIns +cp -R ${DERIVED_DATA_PATH}/Build/Products/Release/Cryptomator.docktileplugin ${APP_NAME}.app/Contents/PlugIns/ +rm -rf ${DERIVED_DATA_PATH} + # generate license -mvn -B -Djavafx.platform=mac -f../../../pom.xml license:add-third-party \ +mvn -B -f../../../pom.xml license:add-third-party \ -Dlicense.thirdPartyFilename=license.rtf \ -Dlicense.outputDirectory=dist/mac/dmg/resources \ -Dlicense.fileTemplate=resources/licenseTemplate.ftl \ diff --git a/dist/mac/resources/Info.plist b/dist/mac/resources/Info.plist index 21641c708..d59c3fe73 100644 --- a/dist/mac/resources/Info.plist +++ b/dist/mac/resources/Info.plist @@ -116,5 +116,8 @@ NSSupportsAutomaticGraphicsSwitching + + NSDockTilePlugIn + Cryptomator.docktileplugin diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index 841c1b2b6..6fdee1c9a 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -65,7 +65,7 @@ Write-Host "`$Env:JAVA_HOME=$Env:JAVA_HOME" $copyright = "(C) $CopyrightStartYear - $((Get-Date).Year) $Vendor" # compile -&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin "-Djavafx.platform=win" +&mvn -B -f $buildDir/../../pom.xml clean package -DskipTests -Pwin Copy-Item "$buildDir\..\..\target\$MainJarGlob.jar" -Destination "$buildDir\..\..\target\mods" # add runtime @@ -86,16 +86,16 @@ switch ($archName) { 'ARM64' { $javafxBaseJmod = Join-Path $Env:JAVA_HOME "jmods\javafx.base.jmod" if (!(Test-Path $javafxBaseJmod)) { - Write-Error "JavaFX module not found in JDK. Please ensure full JDK (including jmods) is installed." + Write-Error "JavaFX module not found in JDK. Please ensure a JDK with JavaFX (including jmods) is installed." exit 1 } $jmodPaths = "$Env:JAVA_HOME/jmods" } 'x64' { - $javaFxVersion='24.0.1' + $javaFxVersion='25' $javaFxJmodsUrl = "https://download2.gluonhq.com/openjfx/${javaFxVersion}/openjfx-${javaFxVersion}_windows-x64_bin-jmods.zip" - $javaFxJmodsSHA256 = 'f13d17c7caf88654fc835f1b4e75a9b0f34a888eb8abef381796c0002e63b03f' + $javaFxJmodsSHA256 = 'c8eb9fd039b00e0020cf6c3db8ed7876bf3ee4d27860aa697a247b83b8296ae7' $javaFxJmods = '.\resources\jfxJmods.zip' if( !(Test-Path -Path $javaFxJmods) ) { @@ -133,7 +133,7 @@ if ((& "$Env:JAVA_HOME\bin\jlink" --help | Select-String -Pattern "Linking from --verbose ` --output runtime ` --module-path $jmodPaths ` - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml ` --strip-native-commands ` --no-header-files ` --no-man-pages ` @@ -194,7 +194,7 @@ if ($LASTEXITCODE -ne 0) { } #Create RTF license for msi -&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" ` +&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` "-Dlicense.thirdPartyFilename=license.rtf" ` "-Dlicense.fileTemplate=$buildDir\resources\licenseTemplate.ftl" ` "-Dlicense.outputDirectory=$buildDir\resources\" ` @@ -237,7 +237,7 @@ if ($LASTEXITCODE -ne 0) { } #Create RTF license for bundle -&mvn -B -f $buildDir/../../pom.xml license:add-third-party "-Djavafx.platform=win" ` +&mvn -B -f $buildDir/../../pom.xml license:add-third-party ` "-Dlicense.thirdPartyFilename=license.rtf" ` "-Dlicense.fileTemplate=$buildDir\bundle\resources\licenseTemplate.ftl" ` "-Dlicense.outputDirectory=$buildDir\bundle\resources\" ` diff --git a/pom.xml b/pom.xml index e05dd809d..52097c539 100644 --- a/pom.xml +++ b/pom.xml @@ -42,14 +42,14 @@ 3.0.0 - 3.18.0 - 2.57.1 + 3.19.0 + 2.57.2 2.2 2.20.0 - 24.0.1 + 25 4.5.0 - 9.37.3 - 1.5.18 + 10.5 + 1.5.19 2.0.17 0.8.1 1.9.0 diff --git a/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java new file mode 100644 index 000000000..e5afc5112 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/BackupRestorer.java @@ -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 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); + } + } +} diff --git a/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java b/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java new file mode 100644 index 000000000..359405d15 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/CryptoFsInitializer.java @@ -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); + } +} diff --git a/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java new file mode 100644 index 000000000..7d487ec54 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/MasterkeyService.java @@ -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 detect(Masterkey masterkey, Path vaultPath) { + try (Stream paths = Files.walk(vaultPath.resolve(DATA_DIR_NAME))) { + Optional 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 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(); + } + +} diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java new file mode 100644 index 000000000..a05073dad --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/RecoveryActionType.java @@ -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 +} diff --git a/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java new file mode 100644 index 000000000..be2af245b --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/RecoveryDirectory.java @@ -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; + } + +} diff --git a/src/main/java/org/cryptomator/common/recovery/VaultPreparator.java b/src/main/java/org/cryptomator/common/recovery/VaultPreparator.java new file mode 100644 index 000000000..d99ba50a0 --- /dev/null +++ b/src/main/java/org/cryptomator/common/recovery/VaultPreparator.java @@ -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 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; + } +} diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index 2e1ae4bba..2fa613e3e 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -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; 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); @@ -336,6 +336,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; } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java b/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java index b879b1f81..4a95fe50b 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultConfigCache.java @@ -20,7 +20,7 @@ public class VaultConfigCache { private final VaultSettings settings; private final AtomicReference config; - VaultConfigCache(VaultSettings settings) { + public VaultConfigCache(VaultSettings settings) { this.settings = settings; this.config = new AtomicReference<>(null); } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java index ce1b2433c..e73075d0d 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultListManager.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultListManager.java @@ -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; }; } diff --git a/src/main/java/org/cryptomator/common/vaults/VaultState.java b/src/main/java/org/cryptomator/common/vaults/VaultState.java index ff09c8b82..f8b9b412a 100644 --- a/src/main/java/org/cryptomator/common/vaults/VaultState.java +++ b/src/main/java/org/cryptomator/common/vaults/VaultState.java @@ -25,6 +25,16 @@ public class VaultState extends ObservableValueBase 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 */ diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index ce8c65a37..68607808d 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -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"), // diff --git a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java index f70242d2b..d010ede43 100644 --- a/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java +++ b/src/main/java/org/cryptomator/ui/convertvault/ConvertVaultModule.java @@ -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)); } } diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java index 837bea012..6f3a86026 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -20,6 +20,8 @@ public class Dialogs { private final ResourceBundle resourceBundle; private final StageFactory stageFactory; + private static final String BUTTON_KEY_CLOSE = "generic.button.close"; + @Inject public Dialogs(ResourceBundle resourceBundle, StageFactory stageFactory) { this.resourceBundle = resourceBundle; @@ -47,6 +49,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 +108,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 +122,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); + } + } diff --git a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java index 08f77849e..5efe4abfe 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java +++ b/src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java @@ -30,7 +30,7 @@ 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, // @@ -66,6 +66,7 @@ public class SimpleDialog { private String[] titleArgs; private String messageKey; private String descriptionKey; + private String[] descriptionArgs; private String okButtonKey; private String cancelButtonKey; private FontAwesome5Icon icon; @@ -93,8 +94,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; } diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 8eb221883..74abac546 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -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 { diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java index 89258dc6c..91ff918e3 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationTerminator.java @@ -28,7 +28,7 @@ import static org.cryptomator.common.vaults.VaultState.Value.*; @FxApplicationScoped public class FxApplicationTerminator { - private static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR); + private static final Set STATES_ALLOWING_TERMINATION = EnumSet.of(LOCKED, NEEDS_MIGRATION, MISSING, ERROR, VAULT_CONFIG_MISSING, ALL_MISSING); private static final Set STATES_PREVENT_TERMINATION = EnumSet.of(PROCESSING); private static final Logger LOG = LoggerFactory.getLogger(FxApplicationTerminator.class); diff --git a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java index 9b2231921..4e1c663e9 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java +++ b/src/main/java/org/cryptomator/ui/keyloading/masterkeyfile/ChooseMasterkeyFileController.java @@ -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 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 result, ResourceBundle resourceBundle) { + public ChooseMasterkeyFileController(@KeyLoading Stage window, // + @KeyLoading Vault vault, // + CompletableFuture 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(); } diff --git a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index fa1b441d9..a186fbe71 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -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 diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java index 7e309fdaf..be4f7f78c 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailController.java @@ -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; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java index 6f57a0d17..85e71937b 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultDetailMissingVaultController.java @@ -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 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 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 diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java index 75ce21dfe..9324c8c7b 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListCellController.java @@ -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; diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java index 97c03194f..db667f111 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListContextMenuController.java @@ -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); } @@ -102,6 +104,12 @@ public class VaultListContextMenuController implements FxController { vaultService.reveal(vault); } + @FXML + public void didClickShareVault() { + var vault = Objects.requireNonNull(selectedVault.get()); + appWindows.showShareVaultWindow(vault); + } + // Getter and Setter public ObservableValue selectedVaultUnlockableProperty() { diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index a457ade3f..f25528498 100644 --- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -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 cellSize; private final Dialogs dialogs; + private final VaultComponent.Factory vaultComponentFactory; + private final RecoveryKeyComponent.Factory recoveryKeyWindow; + private final List mountServices; + public ListView 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 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 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 selectedDirectory = chooseValidVaultDirectory(); + if (selectedDirectory.isEmpty()) { + return; + } + + Path path = selectedDirectory.get(); + Optional 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(); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java index 3986fa01d..6bfe36a4f 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -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 recoverScene(); + @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) + Lazy 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 recoverType); } } diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java index 77f191015..456de11f4 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java @@ -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 successScene; + private final Lazy 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 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 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 successScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverykeyExpertSettingsScene, // + @RecoveryKeyWindow Vault vault, // + RecoveryKeyFactory recoveryKeyFactory, // + MasterkeyFileAccess masterkeyFileAccess, // + ExecutorService executor, // + @RecoveryKeyWindow StringProperty recoveryKey, // + @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // + @Named("recoverType") ObjectProperty 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(); diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java new file mode 100644 index 000000000..5e72b8969 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyExpertSettingsController.java @@ -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; + private final Vault vault; + private final ObjectProperty recoverType; + private final IntegerProperty shorteningThreshold; + private final Lazy resetPasswordScene; + private final Lazy createScene; + private final Lazy onBoardingScene; + private final Lazy recoverScene; + private final BooleanBinding validShorteningThreshold; + + @FXML + public CheckBox expertSettingsCheckBox; + @FXML + public NumericTextField shorteningThresholdTextField; + + @Inject + public RecoveryKeyExpertSettingsController(@RecoveryKeyWindow Stage window, // + Lazy application, // + @RecoveryKeyWindow Vault vault, // + @Named("recoverType") ObjectProperty recoverType, // + @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // + @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_CREATE) Lazy createScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy onBoardingScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy 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()); + } + } +} diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java index 06095eebc..809d16b61 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -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 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 recoverType, @Named("cipherCombo") ObjectProperty cipherCombo, @Nullable MasterkeyFileAccess masterkeyFileAccess) { + return new RecoveryKeyValidateController(vault, vaultConfig, recoveryKey, recoveryKeyFactory, masterkeyFileAccess, recoverType, cipherCombo); } @Provides diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java new file mode 100644 index 000000000..dd15413d8 --- /dev/null +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyOnboardingController.java @@ -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 recoverykeyRecoverScene; + private final Lazy recoverykeyExpertSettingsScene; + private final ObjectProperty 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 recoverykeyRecoverScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverykeyExpertSettingsScene, // + @Named("recoverType") ObjectProperty 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(); + } + +} diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java index 944c52043..8eb505d47 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyRecoverController.java @@ -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 resetPasswordScene; + private final Vault vault; + private final Lazy nextScene; + private final Lazy onBoardingScene; + private final ResourceBundle resourceBundle; + public ObjectProperty 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 resetPasswordScene, ResourceBundle resourceBundle) { + public RecoveryKeyRecoverController(@RecoveryKeyWindow Stage window, // + @RecoveryKeyWindow Vault vault, // + ResourceBundle resourceBundle, // + @FxmlScene(FxmlFile.RECOVERYKEY_RESET_PASSWORD) Lazy resetPasswordScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy expertSettingsScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_ONBOARDING) Lazy onBoardingScene, // + @Named("recoverType") ObjectProperty 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 */ diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java index 18a952ea5..0c06ba9b2 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordController.java @@ -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 recoverResetPasswordSuccessScene; + private final Lazy recoverExpertSettingsScene; + private final Lazy recoverykeyRecoverScene; private final FxApplicationWindows appWindows; + private final MasterkeyFileAccess masterkeyFileAccess; + private final VaultListManager vaultListManager; + private final IntegerProperty shorteningThreshold; + private final ObjectProperty recoverType; + private final ObjectProperty 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 recoverResetPasswordSuccessScene, FxApplicationWindows appWindows) { + public RecoveryKeyResetPasswordController(@RecoveryKeyWindow Stage window, // + @RecoveryKeyWindow Vault vault, // + RecoveryKeyFactory recoveryKeyFactory, // + ExecutorService executor, // + @RecoveryKeyWindow StringProperty recoveryKey, // + @FxmlScene(FxmlFile.RECOVERYKEY_EXPERT_SETTINGS) Lazy recoverExpertSettingsScene, // + @FxmlScene(FxmlFile.RECOVERYKEY_RECOVER) Lazy recoverykeyRecoverScene, // + FxApplicationWindows appWindows, // + MasterkeyFileAccess masterkeyFileAccess, // + VaultListManager vaultListManager, // + @Named("shorteningThreshold") IntegerProperty shorteningThreshold, // + @Named("recoverType") ObjectProperty recoverType, // + @Named("cipherCombo") ObjectProperty 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 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 { - 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 */ diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java deleted file mode 100644 index b8b106d8b..000000000 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyResetPasswordSuccessController.java +++ /dev/null @@ -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(); - } - -} diff --git a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java index 4a8224ffe..35f4c15ed 100644 --- a/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java +++ b/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyValidateController.java @@ -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 recoveryKeyInvalid; private final RecoveryKeyFactory recoveryKeyFactory; private final ObjectProperty recoveryKeyState; + private final ObjectProperty cipherCombo; private final AutoCompleter autoCompleter; + private final ObjectProperty 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 recoverType, // + @Named("cipherCombo") ObjectProperty 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); + } + } } } diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java index 95f13d383..1c8d758fc 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java @@ -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 diff --git a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java index dd003d93d..67ae2f42d 100644 --- a/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java +++ b/src/main/java/org/cryptomator/ui/vaultoptions/MasterkeyOptionsController.java @@ -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 diff --git a/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml b/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml index 7ea190fd4..7797b41b2 100644 --- a/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml +++ b/src/main/resources/fxml/convertvault_hubtopassword_convert.fxml @@ -6,7 +6,12 @@ - + + + + + - + + + + + + + + + + @@ -26,7 +40,7 @@