mirror of
https://github.com/bitwarden/android.git
synced 2024-12-18 07:11:51 +03:00
Merge branch 'bitwarden:main' into main
This commit is contained in:
commit
0772b74b25
310 changed files with 5749 additions and 3661 deletions
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
|
@ -68,7 +68,7 @@ jobs:
|
||||||
java-version: ${{ env.JAVA_VERSION }}
|
java-version: ${{ env.JAVA_VERSION }}
|
||||||
|
|
||||||
- name: Configure Ruby
|
- name: Configure Ruby
|
||||||
uses: ruby/setup-ruby@7bae1d00b5db9166f4f0fc47985a3a5702cb58f0 # v1.197.0
|
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
|
||||||
with:
|
with:
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Configure Ruby
|
- name: Configure Ruby
|
||||||
uses: ruby/setup-ruby@7bae1d00b5db9166f4f0fc47985a3a5702cb58f0 # v1.197.0
|
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
|
||||||
with:
|
with:
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
|
@ -396,7 +396,7 @@ jobs:
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Configure Ruby
|
- name: Configure Ruby
|
||||||
uses: ruby/setup-ruby@7bae1d00b5db9166f4f0fc47985a3a5702cb58f0 # v1.197.0
|
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
|
||||||
with:
|
with:
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
|
@ -464,10 +464,17 @@ jobs:
|
||||||
- name: Increment version
|
- name: Increment version
|
||||||
run: |
|
run: |
|
||||||
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
|
DEFAULT_VERSION_CODE=$((11000+$GITHUB_RUN_NUMBER))
|
||||||
|
VERSION_CODE="${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }}"
|
||||||
bundle exec fastlane setBuildVersionInfo \
|
bundle exec fastlane setBuildVersionInfo \
|
||||||
versionCode:${{ inputs.version-code || '$DEFAULT_VERSION_CODE' }} \
|
versionCode:$VERSION_CODE \
|
||||||
versionName:${{ inputs.version-name || '' }}
|
versionName:${{ inputs.version-name || '' }}
|
||||||
|
|
||||||
|
regex='versionName = "([^"]+)"'
|
||||||
|
if [[ "$(cat app/build.gradle.kts)" =~ $regex ]]; then
|
||||||
|
VERSION_NAME="${BASH_REMATCH[1]}"
|
||||||
|
fi
|
||||||
|
echo "Version Name: ${VERSION_NAME}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Version Number: $VERSION_CODE" >> $GITHUB_STEP_SUMMARY
|
||||||
- name: Generate F-Droid artifacts
|
- name: Generate F-Droid artifacts
|
||||||
env:
|
env:
|
||||||
FDROID_STORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
|
FDROID_STORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
|
||||||
|
@ -528,11 +535,11 @@ jobs:
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Install Firebase app distribution plugin
|
- name: Install Firebase app distribution plugin
|
||||||
if: ${{ inputs.distribute_to_firebase || github.event_name == 'push' }}
|
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||||
run: bundle exec fastlane add_plugin firebase_app_distribution
|
run: bundle exec fastlane add_plugin firebase_app_distribution
|
||||||
|
|
||||||
- name: Publish release F-Droid artifacts to Firebase
|
- name: Publish release F-Droid artifacts to Firebase
|
||||||
if: ${{ inputs.distribute_to_firebase || github.event_name == 'push' }}
|
if: ${{ inputs.distribute-to-firebase || github.event_name == 'push' }}
|
||||||
env:
|
env:
|
||||||
APP_FDROID_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json
|
APP_FDROID_FIREBASE_CREDS_PATH: ${{ github.workspace }}/secrets/app_fdroid_firebase-creds.json
|
||||||
run: |
|
run: |
|
||||||
|
|
11
.github/workflows/crowdin-pull.yml
vendored
11
.github/workflows/crowdin-pull.yml
vendored
|
@ -2,7 +2,7 @@ name: Crowdin Sync
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs: { }
|
inputs: {}
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 0 * * 5'
|
- cron: '0 0 * * 5'
|
||||||
|
|
||||||
|
@ -28,10 +28,17 @@ jobs:
|
||||||
keyvault: "bitwarden-ci"
|
keyvault: "bitwarden-ci"
|
||||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||||
|
|
||||||
|
- name: Generate GH App token
|
||||||
|
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.BW_GHAPP_ID }}
|
||||||
|
private-key: ${{ secrets.BW_GHAPP_KEY }}
|
||||||
|
|
||||||
- name: Download translations
|
- name: Download translations
|
||||||
uses: crowdin/github-action@2d540f18b0a416b1fbf2ee5be35841bd380fc1da # v2.3.0
|
uses: crowdin/github-action@2d540f18b0a416b1fbf2ee5be35841bd380fc1da # v2.3.0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||||
with:
|
with:
|
||||||
config: crowdin.yml
|
config: crowdin.yml
|
||||||
|
|
127
.github/workflows/github-release.yml
vendored
Normal file
127
.github/workflows/github-release.yml
vendored
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
name: Create GitHub Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version-name:
|
||||||
|
description: 'Version Name - E.g. "2024.11.1"'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
version-number:
|
||||||
|
description: 'Version Number - E.g. "123456"'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
artifact_run_id:
|
||||||
|
description: 'GitHub Action Run ID containing artifacts'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
draft:
|
||||||
|
description: 'Create as draft release'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
prerelease:
|
||||||
|
description: 'Mark as pre-release'
|
||||||
|
type: boolean
|
||||||
|
make_latest:
|
||||||
|
description: 'Set as the latest release'
|
||||||
|
type: boolean
|
||||||
|
branch-protection-type:
|
||||||
|
description: 'Branch protection type'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- Branch Name
|
||||||
|
- GitHub API
|
||||||
|
default: Branch Name
|
||||||
|
env:
|
||||||
|
ARTIFACTS_PATH: artifacts
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
actions: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get branch from workflow run
|
||||||
|
id: get_release_branch
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
|
||||||
|
BRANCH_PROTECTION_TYPE: ${{ inputs.branch-protection-type }}
|
||||||
|
run: |
|
||||||
|
release_branch=$(gh run view $ARTIFACT_RUN_ID --json headBranch -q .headBranch)
|
||||||
|
|
||||||
|
case "$BRANCH_PROTECTION_TYPE" in
|
||||||
|
"Branch Name")
|
||||||
|
if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then
|
||||||
|
echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"GitHub API")
|
||||||
|
#NOTE requires token with "administration:read" scope
|
||||||
|
if ! gh api "repos/${{ github.repository }}/branches/$release_branch/protection" | grep -q "required_status_checks"; then
|
||||||
|
echo "::error::Branch '$release_branch' is not protected. Releases must be created from protected branches. If that's not correct, confirm if the github token user has the 'administration:read' scope."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "::error::Unsupported branch protection type: $BRANCH_PROTECTION_TYPE"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "release_branch=$release_branch" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Download artifacts
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
|
||||||
|
run: |
|
||||||
|
gh run download $ARTIFACT_RUN_ID -D $ARTIFACTS_PATH
|
||||||
|
file_count=$(find $ARTIFACTS_PATH -type f | wc -l)
|
||||||
|
echo "Downloaded $file_count file(s)."
|
||||||
|
if [ "$file_count" -gt 0 ]; then
|
||||||
|
echo "Downloaded files:"
|
||||||
|
find $ARTIFACTS_PATH -type f
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # v2.0.9
|
||||||
|
with:
|
||||||
|
tag_name: ${{ inputs.version-name }}
|
||||||
|
name: "v${{ inputs.version-name }} (${{ inputs.version-number }})"
|
||||||
|
prerelease: ${{ inputs.prerelease }}
|
||||||
|
draft: ${{ inputs.draft }}
|
||||||
|
make_latest: ${{ inputs.make_latest }}
|
||||||
|
target_commitish: ${{ steps.get_release_branch.outputs.release_branch }}
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
artifacts/**/*
|
||||||
|
|
||||||
|
- name: Update Release Description
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
RELEASE_ID: ${{ steps.create_release.outputs.id }}
|
||||||
|
RELEASE_URL: ${{ steps.create_release.outputs.url }}
|
||||||
|
ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }}
|
||||||
|
run: |
|
||||||
|
# Get current release body
|
||||||
|
current_body=$(gh api /repos/${{ github.repository }}/releases/$RELEASE_ID --jq .body)
|
||||||
|
|
||||||
|
# Append build source to the end
|
||||||
|
updated_body="${current_body}
|
||||||
|
**Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID"
|
||||||
|
|
||||||
|
# Update release
|
||||||
|
gh api --method PATCH /repos/${{ github.repository }}/releases/$RELEASE_ID \
|
||||||
|
-f body="$updated_body"
|
||||||
|
|
||||||
|
echo "# :rocket: Release ready at:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "$RELEASE_URL" >> $GITHUB_STEP_SUMMARY
|
56
.github/workflows/release-branch.yml
vendored
Normal file
56
.github/workflows/release-branch.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
name: Cut Release Branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_type:
|
||||||
|
description: 'Release Type'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- RC
|
||||||
|
- Hotfix
|
||||||
|
rc_prefix_date:
|
||||||
|
description: 'RC - Prefix with date. E.g. 2024.11-rc1'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release-branch:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
|
- name: Check out repository
|
||||||
|
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Create RC Branch
|
||||||
|
if: inputs.release_type == 'RC'
|
||||||
|
env:
|
||||||
|
RC_PREFIX_DATE: ${{ inputs.rc_prefix_date }}
|
||||||
|
run: |
|
||||||
|
if [ "$RC_PREFIX_DATE" = "true" ]; then
|
||||||
|
current_date=$(date +'%Y.%m')
|
||||||
|
branch_name="release/${current_date}-rc${{ github.run_number }}"
|
||||||
|
else
|
||||||
|
branch_name="release/rc${{ github.run_number }}"
|
||||||
|
fi
|
||||||
|
git switch main
|
||||||
|
git switch -c $branch_name
|
||||||
|
git push origin $branch_name
|
||||||
|
echo "# :cherry_blossom: RC branch: ${branch_name}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Create Hotfix Branch
|
||||||
|
if: inputs.release_type == 'Hotfix'
|
||||||
|
run: |
|
||||||
|
latest_tag=$(git describe --tags --abbrev=0)
|
||||||
|
if [ -z "$latest_tag" ]; then
|
||||||
|
echo "::error::No tags found in the repository"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
branch_name="release/hotfix-${latest_tag}"
|
||||||
|
git switch -c $branch_name $latest_tag
|
||||||
|
git push origin $branch_name
|
||||||
|
echo "# :fire: Hotfix branch: ${branch_name}" >> $GITHUB_STEP_SUMMARY
|
4
.github/workflows/scan.yml
vendored
4
.github/workflows/scan.yml
vendored
|
@ -33,7 +33,7 @@ jobs:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
|
|
||||||
- name: Scan with Checkmarx
|
- name: Scan with Checkmarx
|
||||||
uses: checkmarx/ast-github-action@f0869bd1a37fddc06499a096101e6c900e815d81 # 2.0.36
|
uses: checkmarx/ast-github-action@03a90e7253dadd7e2fff55f5dfbce647b39040a1 # 2.0.37
|
||||||
env:
|
env:
|
||||||
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
INCREMENTAL: "${{ contains(github.event_name, 'pull_request') && '--sast-incremental' || '' }}"
|
||||||
with:
|
with:
|
||||||
|
@ -48,7 +48,7 @@ jobs:
|
||||||
--output-path . ${{ env.INCREMENTAL }}
|
--output-path . ${{ env.INCREMENTAL }}
|
||||||
|
|
||||||
- name: Upload Checkmarx results to GitHub
|
- name: Upload Checkmarx results to GitHub
|
||||||
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
|
uses: github/codeql-action/upload-sarif@9278e421667d5d90a2839487a482448c4ec7df4d # v3.27.2
|
||||||
with:
|
with:
|
||||||
sarif_file: cx_result.sarif
|
sarif_file: cx_result.sarif
|
||||||
|
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -60,7 +60,7 @@ jobs:
|
||||||
${{ runner.os }}-build-
|
${{ runner.os }}-build-
|
||||||
|
|
||||||
- name: Configure Ruby
|
- name: Configure Ruby
|
||||||
uses: ruby/setup-ruby@7bae1d00b5db9166f4f0fc47985a3a5702cb58f0 # v1.197.0
|
uses: ruby/setup-ruby@a2bbe5b1b236842c1cb7dd11e8e3b51e0a616acc # v1.202.0
|
||||||
with:
|
with:
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
|
|
16
Gemfile.lock
16
Gemfile.lock
|
@ -10,8 +10,8 @@ GEM
|
||||||
artifactory (3.0.17)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.996.0)
|
aws-partitions (1.1003.0)
|
||||||
aws-sdk-core (3.211.0)
|
aws-sdk-core (3.212.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
|
@ -19,7 +19,7 @@ GEM
|
||||||
aws-sdk-kms (1.95.0)
|
aws-sdk-kms (1.95.0)
|
||||||
aws-sdk-core (~> 3, >= 3.210.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.169.0)
|
aws-sdk-s3 (1.170.0)
|
||||||
aws-sdk-core (~> 3, >= 3.210.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
|
@ -32,7 +32,7 @@ GEM
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
commander (4.6.0)
|
commander (4.6.0)
|
||||||
highline (~> 2.0.0)
|
highline (~> 2.0.0)
|
||||||
date (3.3.4)
|
date (3.4.0)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
digest-crc (0.6.5)
|
digest-crc (0.6.5)
|
||||||
rake (>= 12.0.0, < 14.0.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
|
@ -162,7 +162,7 @@ GEM
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.4)
|
json (2.8.1)
|
||||||
jwt (2.9.3)
|
jwt (2.9.3)
|
||||||
base64
|
base64
|
||||||
mini_magick (4.13.2)
|
mini_magick (4.13.2)
|
||||||
|
@ -172,7 +172,7 @@ GEM
|
||||||
nanaimo (0.4.0)
|
nanaimo (0.4.0)
|
||||||
naturally (2.2.1)
|
naturally (2.2.1)
|
||||||
nkf (0.2.0)
|
nkf (0.2.0)
|
||||||
optparse (0.5.0)
|
optparse (0.6.0)
|
||||||
os (1.1.4)
|
os (1.1.4)
|
||||||
plist (3.7.1)
|
plist (3.7.1)
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
|
@ -199,7 +199,7 @@ GEM
|
||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
time (0.4.0)
|
time (0.4.1)
|
||||||
date
|
date
|
||||||
trailblazer-option (0.1.2)
|
trailblazer-option (0.1.2)
|
||||||
tty-cursor (0.7.1)
|
tty-cursor (0.7.1)
|
||||||
|
@ -209,7 +209,7 @@ GEM
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.6.0)
|
unicode-display_width (2.6.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.26.0)
|
xcodeproj (1.27.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
- **Minimum SDK**: 29
|
- **Minimum SDK**: 29
|
||||||
- **Target SDK**: 34
|
- **Target SDK**: 35
|
||||||
- **Device Types Supported**: Phone and Tablet
|
- **Device Types Supported**: Phone and Tablet
|
||||||
- **Orientations Supported**: Portrait and Landscape
|
- **Orientations Supported**: Portrait and Landscape
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "ee158c483edfe5102504670f3d9845d4",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "ciphers",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `cipher_type` TEXT NOT NULL, `cipher_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "user_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "cipherType",
|
||||||
|
"columnName": "cipher_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "cipherJson",
|
||||||
|
"columnName": "cipher_json",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_ciphers_user_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_ciphers_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "collections",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `organization_id` TEXT NOT NULL, `should_hide_passwords` INTEGER NOT NULL, `name` TEXT NOT NULL, `external_id` TEXT, `read_only` INTEGER NOT NULL, `manage` INTEGER, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "user_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "organizationId",
|
||||||
|
"columnName": "organization_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shouldHidePasswords",
|
||||||
|
"columnName": "should_hide_passwords",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "externalId",
|
||||||
|
"columnName": "external_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "isReadOnly",
|
||||||
|
"columnName": "read_only",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "canManage",
|
||||||
|
"columnName": "manage",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_collections_user_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_collections_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "domains",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`user_id` TEXT NOT NULL, `domains_json` TEXT, PRIMARY KEY(`user_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "user_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "domainsJson",
|
||||||
|
"columnName": "domains_json",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"user_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "folders",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `name` TEXT, `revision_date` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "user_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "revisionDate",
|
||||||
|
"columnName": "revision_date",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_folders_user_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_folders_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "sends",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` TEXT NOT NULL, `send_type` TEXT NOT NULL, `send_json` TEXT NOT NULL, PRIMARY KEY(`id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userId",
|
||||||
|
"columnName": "user_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sendType",
|
||||||
|
"columnName": "send_type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "sendJson",
|
||||||
|
"columnName": "send_json",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": false,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_sends_user_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_sends_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee158c483edfe5102504670f3d9845d4')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,6 @@ import androidx.core.os.LocaleListCompat
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
||||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
||||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||||
|
@ -39,9 +38,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val mainViewModel: MainViewModel by viewModels()
|
private val mainViewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var accessibilityActivityManager: AccessibilityActivityManager
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var autofillActivityManager: AutofillActivityManager
|
lateinit var autofillActivityManager: AutofillActivityManager
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,21 @@ import kotlinx.serialization.Serializable
|
||||||
* Container for the user's API tokens.
|
* Container for the user's API tokens.
|
||||||
*
|
*
|
||||||
* @property requestId The ID of the pending Auth Request.
|
* @property requestId The ID of the pending Auth Request.
|
||||||
* @property requestPrivateKey The private of the pending Auth Request.
|
* @property requestPrivateKey The private key of the pending Auth Request.
|
||||||
|
* @property requestAccessCode The access code of the pending Auth Request.
|
||||||
|
* @property requestFingerprint The fingerprint of the pending Auth Request.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PendingAuthRequestJson(
|
data class PendingAuthRequestJson(
|
||||||
@SerialName("Id")
|
@SerialName("id")
|
||||||
val requestId: String,
|
val requestId: String,
|
||||||
|
|
||||||
@SerialName("PrivateKey")
|
@SerialName("privateKey")
|
||||||
val requestPrivateKey: String,
|
val requestPrivateKey: String,
|
||||||
|
|
||||||
|
@SerialName("accessCode")
|
||||||
|
val requestAccessCode: String,
|
||||||
|
|
||||||
|
@SerialName("fingerprint")
|
||||||
|
val requestFingerprint: String,
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountReque
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.HTTP
|
import retrofit2.http.HTTP
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
@ -18,43 +19,43 @@ interface AuthenticatedAccountsApi {
|
||||||
* Converts the currently active account to a key-connector account.
|
* Converts the currently active account to a key-connector account.
|
||||||
*/
|
*/
|
||||||
@POST("/accounts/convert-to-key-connector")
|
@POST("/accounts/convert-to-key-connector")
|
||||||
suspend fun convertToKeyConnector(): Result<Unit>
|
suspend fun convertToKeyConnector(): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the keys for the current account.
|
* Creates the keys for the current account.
|
||||||
*/
|
*/
|
||||||
@POST("/accounts/keys")
|
@POST("/accounts/keys")
|
||||||
suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): Result<Unit>
|
suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the current account.
|
* Deletes the current account.
|
||||||
*/
|
*/
|
||||||
@HTTP(method = "DELETE", path = "/accounts", hasBody = true)
|
@HTTP(method = "DELETE", path = "/accounts", hasBody = true)
|
||||||
suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): Result<Unit>
|
suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): NetworkResult<Unit>
|
||||||
|
|
||||||
@POST("/accounts/request-otp")
|
@POST("/accounts/request-otp")
|
||||||
suspend fun requestOtp(): Result<Unit>
|
suspend fun requestOtp(): NetworkResult<Unit>
|
||||||
|
|
||||||
@POST("/accounts/verify-otp")
|
@POST("/accounts/verify-otp")
|
||||||
suspend fun verifyOtp(
|
suspend fun verifyOtp(
|
||||||
@Body body: VerifyOtpRequestJson,
|
@Body body: VerifyOtpRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the temporary password.
|
* Resets the temporary password.
|
||||||
*/
|
*/
|
||||||
@HTTP(method = "PUT", path = "/accounts/update-temp-password", hasBody = true)
|
@HTTP(method = "PUT", path = "/accounts/update-temp-password", hasBody = true)
|
||||||
suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): Result<Unit>
|
suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the password.
|
* Resets the password.
|
||||||
*/
|
*/
|
||||||
@HTTP(method = "POST", path = "/accounts/password", hasBody = true)
|
@HTTP(method = "POST", path = "/accounts/password", hasBody = true)
|
||||||
suspend fun resetPassword(@Body body: ResetPasswordRequestJson): Result<Unit>
|
suspend fun resetPassword(@Body body: ResetPasswordRequestJson): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the password.
|
* Sets the password.
|
||||||
*/
|
*/
|
||||||
@POST("/accounts/set-password")
|
@POST("/accounts/set-password")
|
||||||
suspend fun setPassword(@Body body: SetPasswordRequestJson): Result<Unit>
|
suspend fun setPassword(@Body body: SetPasswordRequestJson): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
@ -22,7 +23,7 @@ interface AuthenticatedAuthRequestsApi {
|
||||||
suspend fun createAdminAuthRequest(
|
suspend fun createAdminAuthRequest(
|
||||||
@Header("Device-Identifier") deviceIdentifier: String,
|
@Header("Device-Identifier") deviceIdentifier: String,
|
||||||
@Body body: AuthRequestRequestJson,
|
@Body body: AuthRequestRequestJson,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates an authentication request.
|
* Updates an authentication request.
|
||||||
|
@ -31,13 +32,13 @@ interface AuthenticatedAuthRequestsApi {
|
||||||
suspend fun updateAuthRequest(
|
suspend fun updateAuthRequest(
|
||||||
@Path("id") userId: String,
|
@Path("id") userId: String,
|
||||||
@Body body: AuthRequestUpdateRequestJson,
|
@Body body: AuthRequestUpdateRequestJson,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of auth requests for this device.
|
* Gets a list of auth requests for this device.
|
||||||
*/
|
*/
|
||||||
@GET("/auth-requests")
|
@GET("/auth-requests")
|
||||||
suspend fun getAuthRequests(): Result<AuthRequestsResponseJson>
|
suspend fun getAuthRequests(): NetworkResult<AuthRequestsResponseJson>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an existing authentication request by ID.
|
* Retrieves an existing authentication request by ID.
|
||||||
|
@ -45,5 +46,5 @@ interface AuthenticatedAuthRequestsApi {
|
||||||
@GET("/auth-requests/{requestId}")
|
@GET("/auth-requests/{requestId}")
|
||||||
suspend fun getAuthRequest(
|
suspend fun getAuthRequest(
|
||||||
@Path("requestId") requestId: String,
|
@Path("requestId") requestId: String,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
@ -16,5 +17,5 @@ interface AuthenticatedDevicesApi {
|
||||||
suspend fun updateTrustedDeviceKeys(
|
suspend fun updateTrustedDeviceKeys(
|
||||||
@Path(value = "appId") appId: String,
|
@Path(value = "appId") appId: String,
|
||||||
@Body request: TrustedDeviceKeysRequestJson,
|
@Body request: TrustedDeviceKeysRequestJson,
|
||||||
): Result<TrustedDeviceKeysResponseJson>
|
): NetworkResult<TrustedDeviceKeysResponseJson>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
@ -15,5 +16,5 @@ interface AuthenticatedKeyConnectorApi {
|
||||||
suspend fun storeMasterKeyToKeyConnector(
|
suspend fun storeMasterKeyToKeyConnector(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Body body: KeyConnectorMasterKeyRequestJson,
|
@Body body: KeyConnectorMasterKeyRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
|
@ -20,7 +21,7 @@ interface AuthenticatedOrganizationApi {
|
||||||
@Path("orgId") organizationId: String,
|
@Path("orgId") organizationId: String,
|
||||||
@Path("userId") userId: String,
|
@Path("userId") userId: String,
|
||||||
@Body body: OrganizationResetPasswordEnrollRequestJson,
|
@Body body: OrganizationResetPasswordEnrollRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether this organization auto enrolls users in password reset.
|
* Checks whether this organization auto enrolls users in password reset.
|
||||||
|
@ -28,7 +29,7 @@ interface AuthenticatedOrganizationApi {
|
||||||
@GET("/organizations/{identifier}/auto-enroll-status")
|
@GET("/organizations/{identifier}/auto-enroll-status")
|
||||||
suspend fun getOrganizationAutoEnrollResponse(
|
suspend fun getOrganizationAutoEnrollResponse(
|
||||||
@Path("identifier") organizationIdentifier: String,
|
@Path("identifier") organizationIdentifier: String,
|
||||||
): Result<OrganizationAutoEnrollStatusResponseJson>
|
): NetworkResult<OrganizationAutoEnrollStatusResponseJson>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the public and private keys for this organization.
|
* Gets the public and private keys for this organization.
|
||||||
|
@ -36,5 +37,5 @@ interface AuthenticatedOrganizationApi {
|
||||||
@GET("/organizations/{id}/keys")
|
@GET("/organizations/{id}/keys")
|
||||||
suspend fun getOrganizationKeys(
|
suspend fun getOrganizationKeys(
|
||||||
@Path("id") organizationId: String,
|
@Path("id") organizationId: String,
|
||||||
): Result<OrganizationKeysResponseJson>
|
): NetworkResult<OrganizationKeysResponseJson>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
@ -14,5 +15,5 @@ interface HaveIBeenPwnedApi {
|
||||||
suspend fun fetchBreachedPasswords(
|
suspend fun fetchBreachedPasswords(
|
||||||
@Path("hashPrefix")
|
@Path("hashPrefix")
|
||||||
hashPrefix: String,
|
hashPrefix: String,
|
||||||
): Result<ResponseBody>
|
): NetworkResult<ResponseBody>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
@ -15,16 +16,16 @@ interface UnauthenticatedAccountsApi {
|
||||||
@POST("/accounts/password-hint")
|
@POST("/accounts/password-hint")
|
||||||
suspend fun passwordHintRequest(
|
suspend fun passwordHintRequest(
|
||||||
@Body body: PasswordHintRequestJson,
|
@Body body: PasswordHintRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
@POST("/two-factor/send-email-login")
|
@POST("/two-factor/send-email-login")
|
||||||
suspend fun resendVerificationCodeEmail(
|
suspend fun resendVerificationCodeEmail(
|
||||||
@Body body: ResendEmailRequestJson,
|
@Body body: ResendEmailRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
@POST("/accounts/set-key-connector-key")
|
@POST("/accounts/set-key-connector-key")
|
||||||
suspend fun setKeyConnectorKey(
|
suspend fun setKeyConnectorKey(
|
||||||
@Body body: KeyConnectorKeyRequestJson,
|
@Body body: KeyConnectorKeyRequestJson,
|
||||||
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
@ -21,7 +22,7 @@ interface UnauthenticatedAuthRequestsApi {
|
||||||
suspend fun createAuthRequest(
|
suspend fun createAuthRequest(
|
||||||
@Header("Device-Identifier") deviceIdentifier: String,
|
@Header("Device-Identifier") deviceIdentifier: String,
|
||||||
@Body body: AuthRequestRequestJson,
|
@Body body: AuthRequestRequestJson,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries for updates to a given auth request.
|
* Queries for updates to a given auth request.
|
||||||
|
@ -30,5 +31,5 @@ interface UnauthenticatedAuthRequestsApi {
|
||||||
suspend fun getAuthRequestUpdate(
|
suspend fun getAuthRequestUpdate(
|
||||||
@Path("requestId") requestId: String,
|
@Path("requestId") requestId: String,
|
||||||
@Query("code") accessCode: String,
|
@Query("code") accessCode: String,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest>
|
): NetworkResult<AuthRequestsResponseJson.AuthRequest>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
|
||||||
|
@ -11,5 +12,5 @@ interface UnauthenticatedDevicesApi {
|
||||||
suspend fun getIsKnownDevice(
|
suspend fun getIsKnownDevice(
|
||||||
@Header(value = "X-Request-Email") emailAddress: String,
|
@Header(value = "X-Request-Email") emailAddress: String,
|
||||||
@Header(value = "X-Device-Identifier") deviceId: String,
|
@Header(value = "X-Device-Identifier") deviceId: String,
|
||||||
): Result<Boolean>
|
): NetworkResult<Boolean>
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJso
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
|
@ -46,12 +47,12 @@ interface UnauthenticatedIdentityApi {
|
||||||
@Field(value = "twoFactorProvider") twoFactorMethod: String?,
|
@Field(value = "twoFactorProvider") twoFactorMethod: String?,
|
||||||
@Field(value = "twoFactorRemember") twoFactorRemember: String?,
|
@Field(value = "twoFactorRemember") twoFactorRemember: String?,
|
||||||
@Field(value = "authRequest") authRequestId: String?,
|
@Field(value = "authRequest") authRequestId: String?,
|
||||||
): Result<GetTokenResponseJson.Success>
|
): NetworkResult<GetTokenResponseJson.Success>
|
||||||
|
|
||||||
@GET("/sso/prevalidate")
|
@GET("/sso/prevalidate")
|
||||||
suspend fun prevalidateSso(
|
suspend fun prevalidateSso(
|
||||||
@Query("domainHint") organizationIdentifier: String,
|
@Query("domainHint") organizationIdentifier: String,
|
||||||
): Result<PrevalidateSsoResponseJson>
|
): NetworkResult<PrevalidateSsoResponseJson>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This call needs to be synchronous so we need it to return a [Call] directly. The identity
|
* This call needs to be synchronous so we need it to return a [Call] directly. The identity
|
||||||
|
@ -66,23 +67,25 @@ interface UnauthenticatedIdentityApi {
|
||||||
): Call<RefreshTokenResponseJson>
|
): Call<RefreshTokenResponseJson>
|
||||||
|
|
||||||
@POST("/accounts/prelogin")
|
@POST("/accounts/prelogin")
|
||||||
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson>
|
suspend fun preLogin(@Body body: PreLoginRequestJson): NetworkResult<PreLoginResponseJson>
|
||||||
|
|
||||||
@POST("/accounts/register")
|
@POST("/accounts/register")
|
||||||
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success>
|
suspend fun register(
|
||||||
|
@Body body: RegisterRequestJson,
|
||||||
|
): NetworkResult<RegisterResponseJson.Success>
|
||||||
|
|
||||||
@POST("/accounts/register/finish")
|
@POST("/accounts/register/finish")
|
||||||
suspend fun registerFinish(
|
suspend fun registerFinish(
|
||||||
@Body body: RegisterFinishRequestJson,
|
@Body body: RegisterFinishRequestJson,
|
||||||
): Result<RegisterResponseJson.Success>
|
): NetworkResult<RegisterResponseJson.Success>
|
||||||
|
|
||||||
@POST("/accounts/register/send-verification-email")
|
@POST("/accounts/register/send-verification-email")
|
||||||
suspend fun sendVerificationEmail(
|
suspend fun sendVerificationEmail(
|
||||||
@Body body: SendVerificationEmailRequestJson,
|
@Body body: SendVerificationEmailRequestJson,
|
||||||
): Result<JsonPrimitive?>
|
): NetworkResult<JsonPrimitive?>
|
||||||
|
|
||||||
@POST("/accounts/register/verification-email-clicked")
|
@POST("/accounts/register/verification-email-clicked")
|
||||||
suspend fun verifyEmailToken(
|
suspend fun verifyEmailToken(
|
||||||
@Body body: VerifyEmailTokenRequestJson,
|
@Body body: VerifyEmailTokenRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
@ -20,11 +21,11 @@ interface UnauthenticatedKeyConnectorApi {
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
||||||
@Body body: KeyConnectorMasterKeyRequestJson,
|
@Body body: KeyConnectorMasterKeyRequestJson,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
suspend fun getMasterKeyFromKeyConnector(
|
suspend fun getMasterKeyFromKeyConnector(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
|
||||||
): Result<KeyConnectorMasterKeyResponseJson>
|
): NetworkResult<KeyConnectorMasterKeyResponseJson>
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomain
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ interface UnauthenticatedOrganizationApi {
|
||||||
@POST("/organizations/domain/sso/details")
|
@POST("/organizations/domain/sso/details")
|
||||||
suspend fun getClaimedDomainOrganizationDetails(
|
suspend fun getClaimedDomainOrganizationDetails(
|
||||||
@Body body: OrganizationDomainSsoDetailsRequestJson,
|
@Body body: OrganizationDomainSsoDetailsRequestJson,
|
||||||
): Result<OrganizationDomainSsoDetailsResponseJson>
|
): NetworkResult<OrganizationDomainSsoDetailsResponseJson>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for the verfied organization domains of an email for SSO purposes.
|
* Checks for the verfied organization domains of an email for SSO purposes.
|
||||||
|
@ -25,5 +26,5 @@ interface UnauthenticatedOrganizationApi {
|
||||||
@POST("/organizations/domain/sso/verified")
|
@POST("/organizations/domain/sso/verified")
|
||||||
suspend fun getVerifiedOrganizationDomainsByEmail(
|
suspend fun getVerifiedOrganizationDomainsByEmail(
|
||||||
@Body body: VerifiedOrganizationDomainSsoDetailsRequest,
|
@Body body: VerifiedOrganizationDomainSsoDetailsRequest,
|
||||||
): Result<VerifiedOrganizationDomainSsoDetailsResponse>
|
): NetworkResult<VerifiedOrganizationDomainSsoDetailsResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJs
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,18 +38,22 @@ class AccountsServiceImpl(
|
||||||
* Converts the currently active account to a key-connector account.
|
* Converts the currently active account to a key-connector account.
|
||||||
*/
|
*/
|
||||||
override suspend fun convertToKeyConnector(): Result<Unit> =
|
override suspend fun convertToKeyConnector(): Result<Unit> =
|
||||||
authenticatedAccountsApi.convertToKeyConnector()
|
authenticatedAccountsApi
|
||||||
|
.convertToKeyConnector()
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun createAccountKeys(
|
override suspend fun createAccountKeys(
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
encryptedPrivateKey: String,
|
encryptedPrivateKey: String,
|
||||||
): Result<Unit> =
|
): Result<Unit> =
|
||||||
authenticatedAccountsApi.createAccountKeys(
|
authenticatedAccountsApi
|
||||||
body = CreateAccountKeysRequest(
|
.createAccountKeys(
|
||||||
publicKey = publicKey,
|
body = CreateAccountKeysRequest(
|
||||||
encryptedPrivateKey = encryptedPrivateKey,
|
publicKey = publicKey,
|
||||||
),
|
encryptedPrivateKey = encryptedPrivateKey,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun deleteAccount(
|
override suspend fun deleteAccount(
|
||||||
masterPasswordHash: String?,
|
masterPasswordHash: String?,
|
||||||
|
@ -61,9 +66,8 @@ class AccountsServiceImpl(
|
||||||
oneTimePassword = oneTimePassword,
|
oneTimePassword = oneTimePassword,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.map {
|
.toResult()
|
||||||
DeleteAccountResponseJson.Success
|
.map { DeleteAccountResponseJson.Success }
|
||||||
}
|
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable
|
throwable
|
||||||
.toBitwardenError()
|
.toBitwardenError()
|
||||||
|
@ -75,20 +79,25 @@ class AccountsServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun requestOneTimePasscode(): Result<Unit> =
|
override suspend fun requestOneTimePasscode(): Result<Unit> =
|
||||||
authenticatedAccountsApi.requestOtp()
|
authenticatedAccountsApi
|
||||||
|
.requestOtp()
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun verifyOneTimePasscode(passcode: String): Result<Unit> =
|
override suspend fun verifyOneTimePasscode(passcode: String): Result<Unit> =
|
||||||
authenticatedAccountsApi.verifyOtp(
|
authenticatedAccountsApi
|
||||||
VerifyOtpRequestJson(
|
.verifyOtp(
|
||||||
oneTimePasscode = passcode,
|
VerifyOtpRequestJson(
|
||||||
),
|
oneTimePasscode = passcode,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun requestPasswordHint(
|
override suspend fun requestPasswordHint(
|
||||||
email: String,
|
email: String,
|
||||||
): Result<PasswordHintResponseJson> =
|
): Result<PasswordHintResponseJson> =
|
||||||
unauthenticatedAccountsApi
|
unauthenticatedAccountsApi
|
||||||
.passwordHintRequest(PasswordHintRequestJson(email))
|
.passwordHintRequest(PasswordHintRequestJson(email))
|
||||||
|
.toResult()
|
||||||
.map { PasswordHintResponseJson.Success }
|
.map { PasswordHintResponseJson.Success }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable
|
throwable
|
||||||
|
@ -101,54 +110,70 @@ class AccountsServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun resendVerificationCodeEmail(body: ResendEmailRequestJson): Result<Unit> =
|
override suspend fun resendVerificationCodeEmail(body: ResendEmailRequestJson): Result<Unit> =
|
||||||
unauthenticatedAccountsApi.resendVerificationCodeEmail(body = body)
|
unauthenticatedAccountsApi
|
||||||
|
.resendVerificationCodeEmail(body = body)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun resetPassword(body: ResetPasswordRequestJson): Result<Unit> {
|
override suspend fun resetPassword(body: ResetPasswordRequestJson): Result<Unit> =
|
||||||
return if (body.currentPasswordHash == null) {
|
if (body.currentPasswordHash == null) {
|
||||||
authenticatedAccountsApi.resetTempPassword(body = body)
|
authenticatedAccountsApi
|
||||||
|
.resetTempPassword(body = body)
|
||||||
|
.toResult()
|
||||||
} else {
|
} else {
|
||||||
authenticatedAccountsApi.resetPassword(body = body)
|
authenticatedAccountsApi
|
||||||
|
.resetPassword(body = body)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun setKeyConnectorKey(
|
override suspend fun setKeyConnectorKey(
|
||||||
accessToken: String,
|
accessToken: String,
|
||||||
body: KeyConnectorKeyRequestJson,
|
body: KeyConnectorKeyRequestJson,
|
||||||
): Result<Unit> = unauthenticatedAccountsApi.setKeyConnectorKey(
|
): Result<Unit> =
|
||||||
body = body,
|
unauthenticatedAccountsApi
|
||||||
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
|
.setKeyConnectorKey(
|
||||||
)
|
body = body,
|
||||||
|
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun setPassword(
|
override suspend fun setPassword(
|
||||||
body: SetPasswordRequestJson,
|
body: SetPasswordRequestJson,
|
||||||
): Result<Unit> = authenticatedAccountsApi.setPassword(body)
|
): Result<Unit> = authenticatedAccountsApi
|
||||||
|
.setPassword(body)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getMasterKeyFromKeyConnector(
|
override suspend fun getMasterKeyFromKeyConnector(
|
||||||
url: String,
|
url: String,
|
||||||
accessToken: String,
|
accessToken: String,
|
||||||
): Result<KeyConnectorMasterKeyResponseJson> =
|
): Result<KeyConnectorMasterKeyResponseJson> =
|
||||||
unauthenticatedKeyConnectorApi.getMasterKeyFromKeyConnector(
|
unauthenticatedKeyConnectorApi
|
||||||
url = "$url/user-keys",
|
.getMasterKeyFromKeyConnector(
|
||||||
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
|
url = "$url/user-keys",
|
||||||
)
|
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun storeMasterKeyToKeyConnector(
|
override suspend fun storeMasterKeyToKeyConnector(
|
||||||
url: String,
|
url: String,
|
||||||
masterKey: String,
|
masterKey: String,
|
||||||
): Result<Unit> =
|
): Result<Unit> =
|
||||||
authenticatedKeyConnectorApi.storeMasterKeyToKeyConnector(
|
authenticatedKeyConnectorApi
|
||||||
url = "$url/user-keys",
|
.storeMasterKeyToKeyConnector(
|
||||||
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
|
url = "$url/user-keys",
|
||||||
)
|
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun storeMasterKeyToKeyConnector(
|
override suspend fun storeMasterKeyToKeyConnector(
|
||||||
url: String,
|
url: String,
|
||||||
accessToken: String,
|
accessToken: String,
|
||||||
masterKey: String,
|
masterKey: String,
|
||||||
): Result<Unit> =
|
): Result<Unit> =
|
||||||
unauthenticatedKeyConnectorApi.storeMasterKeyToKeyConnector(
|
unauthenticatedKeyConnectorApi
|
||||||
url = "$url/user-keys",
|
.storeMasterKeyToKeyConnector(
|
||||||
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
|
url = "$url/user-keys",
|
||||||
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
|
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
|
||||||
)
|
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,17 +3,22 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedAuthRequestsApi
|
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedAuthRequestsApi
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
class AuthRequestsServiceImpl(
|
class AuthRequestsServiceImpl(
|
||||||
private val authenticatedAuthRequestsApi: AuthenticatedAuthRequestsApi,
|
private val authenticatedAuthRequestsApi: AuthenticatedAuthRequestsApi,
|
||||||
) : AuthRequestsService {
|
) : AuthRequestsService {
|
||||||
override suspend fun getAuthRequests(): Result<AuthRequestsResponseJson> =
|
override suspend fun getAuthRequests(): Result<AuthRequestsResponseJson> =
|
||||||
authenticatedAuthRequestsApi.getAuthRequests()
|
authenticatedAuthRequestsApi
|
||||||
|
.getAuthRequests()
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getAuthRequest(
|
override suspend fun getAuthRequest(
|
||||||
requestId: String,
|
requestId: String,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest> =
|
): Result<AuthRequestsResponseJson.AuthRequest> =
|
||||||
authenticatedAuthRequestsApi.getAuthRequest(requestId = requestId)
|
authenticatedAuthRequestsApi
|
||||||
|
.getAuthRequest(requestId = requestId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun updateAuthRequest(
|
override suspend fun updateAuthRequest(
|
||||||
requestId: String,
|
requestId: String,
|
||||||
|
@ -22,13 +27,15 @@ class AuthRequestsServiceImpl(
|
||||||
deviceId: String,
|
deviceId: String,
|
||||||
isApproved: Boolean,
|
isApproved: Boolean,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest> =
|
): Result<AuthRequestsResponseJson.AuthRequest> =
|
||||||
authenticatedAuthRequestsApi.updateAuthRequest(
|
authenticatedAuthRequestsApi
|
||||||
userId = requestId,
|
.updateAuthRequest(
|
||||||
body = AuthRequestUpdateRequestJson(
|
userId = requestId,
|
||||||
key = key,
|
body = AuthRequestUpdateRequestJson(
|
||||||
masterPasswordHash = masterPasswordHash,
|
key = key,
|
||||||
deviceId = deviceId,
|
masterPasswordHash = masterPasswordHash,
|
||||||
isApproved = isApproved,
|
deviceId = deviceId,
|
||||||
),
|
isApproved = isApproved,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedDevic
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
class DevicesServiceImpl(
|
class DevicesServiceImpl(
|
||||||
private val authenticatedDevicesApi: AuthenticatedDevicesApi,
|
private val authenticatedDevicesApi: AuthenticatedDevicesApi,
|
||||||
|
@ -13,22 +14,26 @@ class DevicesServiceImpl(
|
||||||
override suspend fun getIsKnownDevice(
|
override suspend fun getIsKnownDevice(
|
||||||
emailAddress: String,
|
emailAddress: String,
|
||||||
deviceId: String,
|
deviceId: String,
|
||||||
): Result<Boolean> = unauthenticatedDevicesApi.getIsKnownDevice(
|
): Result<Boolean> = unauthenticatedDevicesApi
|
||||||
emailAddress = emailAddress.base64UrlEncode(),
|
.getIsKnownDevice(
|
||||||
deviceId = deviceId,
|
emailAddress = emailAddress.base64UrlEncode(),
|
||||||
)
|
deviceId = deviceId,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun trustDevice(
|
override suspend fun trustDevice(
|
||||||
appId: String,
|
appId: String,
|
||||||
encryptedUserKey: String,
|
encryptedUserKey: String,
|
||||||
encryptedDevicePublicKey: String,
|
encryptedDevicePublicKey: String,
|
||||||
encryptedDevicePrivateKey: String,
|
encryptedDevicePrivateKey: String,
|
||||||
): Result<TrustedDeviceKeysResponseJson> = authenticatedDevicesApi.updateTrustedDeviceKeys(
|
): Result<TrustedDeviceKeysResponseJson> = authenticatedDevicesApi
|
||||||
appId = appId,
|
.updateTrustedDeviceKeys(
|
||||||
request = TrustedDeviceKeysRequestJson(
|
appId = appId,
|
||||||
encryptedUserKey = encryptedUserKey,
|
request = TrustedDeviceKeysRequestJson(
|
||||||
encryptedDevicePublicKey = encryptedDevicePublicKey,
|
encryptedUserKey = encryptedUserKey,
|
||||||
encryptedDevicePrivateKey = encryptedDevicePrivateKey,
|
encryptedDevicePublicKey = encryptedDevicePublicKey,
|
||||||
),
|
encryptedDevicePrivateKey = encryptedDevicePrivateKey,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.HaveIBeenPwnedApi
|
import com.x8bit.bitwarden.data.auth.datasource.network.api.HaveIBeenPwnedApi
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class HaveIBeenPwnedServiceImpl(private val api: HaveIBeenPwnedApi) : HaveIBeenPwnedService {
|
class HaveIBeenPwnedServiceImpl(private val api: HaveIBeenPwnedApi) : HaveIBeenPwnedService {
|
||||||
|
@ -17,6 +18,7 @@ class HaveIBeenPwnedServiceImpl(private val api: HaveIBeenPwnedApi) : HaveIBeenP
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.fetchBreachedPasswords(hashPrefix = hashPrefix)
|
.fetchBreachedPasswords(hashPrefix = hashPrefix)
|
||||||
|
.toResult()
|
||||||
.mapCatching { responseBody ->
|
.mapCatching { responseBody ->
|
||||||
responseBody.string()
|
responseBody.string()
|
||||||
// First split the response by newline: each hashed password is on a new line.
|
// First split the response by newline: each hashed password is on a new line.
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequ
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
|
||||||
|
@ -68,7 +69,7 @@ interface IdentityService {
|
||||||
*/
|
*/
|
||||||
suspend fun sendVerificationEmail(
|
suspend fun sendVerificationEmail(
|
||||||
body: SendVerificationEmailRequestJson,
|
body: SendVerificationEmailRequestJson,
|
||||||
): Result<String?>
|
): Result<SendVerificationEmailResponseJson>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new account to Bitwarden using email verification flow.
|
* Register a new account to Bitwarden using email verification flow.
|
||||||
|
|
|
@ -11,13 +11,15 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequ
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForResult
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForNetworkResult
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.platform.util.DeviceModelProvider
|
import com.x8bit.bitwarden.data.platform.util.DeviceModelProvider
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
@ -28,12 +30,15 @@ class IdentityServiceImpl(
|
||||||
) : IdentityService {
|
) : IdentityService {
|
||||||
|
|
||||||
override suspend fun preLogin(email: String): Result<PreLoginResponseJson> =
|
override suspend fun preLogin(email: String): Result<PreLoginResponseJson> =
|
||||||
unauthenticatedIdentityApi.preLogin(PreLoginRequestJson(email = email))
|
unauthenticatedIdentityApi
|
||||||
|
.preLogin(PreLoginRequestJson(email = email))
|
||||||
|
.toResult()
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
|
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
|
||||||
unauthenticatedIdentityApi
|
unauthenticatedIdentityApi
|
||||||
.register(body)
|
.register(body)
|
||||||
|
.toResult()
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
val bitwardenError = throwable.toBitwardenError()
|
val bitwardenError = throwable.toBitwardenError()
|
||||||
bitwardenError
|
bitwardenError
|
||||||
|
@ -75,6 +80,7 @@ class IdentityServiceImpl(
|
||||||
captchaResponse = captchaToken,
|
captchaResponse = captchaToken,
|
||||||
authRequestId = authModel.authRequestId,
|
authRequestId = authModel.authRequestId,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
val bitwardenError = throwable.toBitwardenError()
|
val bitwardenError = throwable.toBitwardenError()
|
||||||
bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>(
|
bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>(
|
||||||
|
@ -95,6 +101,7 @@ class IdentityServiceImpl(
|
||||||
.prevalidateSso(
|
.prevalidateSso(
|
||||||
organizationIdentifier = organizationIdentifier,
|
organizationIdentifier = organizationIdentifier,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override fun refreshTokenSynchronously(
|
override fun refreshTokenSynchronously(
|
||||||
refreshToken: String,
|
refreshToken: String,
|
||||||
|
@ -104,7 +111,8 @@ class IdentityServiceImpl(
|
||||||
grantType = "refresh_token",
|
grantType = "refresh_token",
|
||||||
refreshToken = refreshToken,
|
refreshToken = refreshToken,
|
||||||
)
|
)
|
||||||
.executeForResult()
|
.executeForNetworkResult()
|
||||||
|
.toResult()
|
||||||
|
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber")
|
||||||
override suspend fun registerFinish(
|
override suspend fun registerFinish(
|
||||||
|
@ -112,6 +120,7 @@ class IdentityServiceImpl(
|
||||||
): Result<RegisterResponseJson> =
|
): Result<RegisterResponseJson> =
|
||||||
unauthenticatedIdentityApi
|
unauthenticatedIdentityApi
|
||||||
.registerFinish(body)
|
.registerFinish(body)
|
||||||
|
.toResult()
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
val bitwardenError = throwable.toBitwardenError()
|
val bitwardenError = throwable.toBitwardenError()
|
||||||
bitwardenError
|
bitwardenError
|
||||||
|
@ -124,10 +133,20 @@ class IdentityServiceImpl(
|
||||||
|
|
||||||
override suspend fun sendVerificationEmail(
|
override suspend fun sendVerificationEmail(
|
||||||
body: SendVerificationEmailRequestJson,
|
body: SendVerificationEmailRequestJson,
|
||||||
): Result<String?> {
|
): Result<SendVerificationEmailResponseJson> {
|
||||||
return unauthenticatedIdentityApi
|
return unauthenticatedIdentityApi
|
||||||
.sendVerificationEmail(body = body)
|
.sendVerificationEmail(body = body)
|
||||||
.map { it?.content }
|
.toResult()
|
||||||
|
.map { SendVerificationEmailResponseJson.Success(it?.content) }
|
||||||
|
.recoverCatching { throwable ->
|
||||||
|
throwable
|
||||||
|
.toBitwardenError()
|
||||||
|
.parseErrorBodyOrNull<SendVerificationEmailResponseJson.Invalid>(
|
||||||
|
code = 400,
|
||||||
|
json = json,
|
||||||
|
)
|
||||||
|
?: throw throwable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun verifyEmailRegistrationToken(
|
override suspend fun verifyEmailRegistrationToken(
|
||||||
|
@ -136,9 +155,8 @@ class IdentityServiceImpl(
|
||||||
.verifyEmailToken(
|
.verifyEmailToken(
|
||||||
body = body,
|
body = body,
|
||||||
)
|
)
|
||||||
.map {
|
.toResult()
|
||||||
VerifyEmailTokenResponseJson.Valid
|
.map { VerifyEmailTokenResponseJson.Valid }
|
||||||
}
|
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
val bitwardenError = throwable.toBitwardenError()
|
val bitwardenError = throwable.toBitwardenError()
|
||||||
bitwardenError
|
bitwardenError
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedAuthR
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,17 +25,19 @@ class NewAuthRequestServiceImpl(
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest> =
|
): Result<AuthRequestsResponseJson.AuthRequest> =
|
||||||
when (authRequestType) {
|
when (authRequestType) {
|
||||||
AuthRequestTypeJson.LOGIN_WITH_DEVICE -> {
|
AuthRequestTypeJson.LOGIN_WITH_DEVICE -> {
|
||||||
unauthenticatedAuthRequestsApi.createAuthRequest(
|
unauthenticatedAuthRequestsApi
|
||||||
deviceIdentifier = deviceId,
|
.createAuthRequest(
|
||||||
body = AuthRequestRequestJson(
|
deviceIdentifier = deviceId,
|
||||||
email = email,
|
body = AuthRequestRequestJson(
|
||||||
publicKey = publicKey,
|
email = email,
|
||||||
deviceId = deviceId,
|
publicKey = publicKey,
|
||||||
accessCode = accessCode,
|
deviceId = deviceId,
|
||||||
fingerprint = fingerprint,
|
accessCode = accessCode,
|
||||||
type = authRequestType,
|
fingerprint = fingerprint,
|
||||||
),
|
type = authRequestType,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthRequestTypeJson.UNLOCK -> {
|
AuthRequestTypeJson.UNLOCK -> {
|
||||||
|
@ -43,17 +46,19 @@ class NewAuthRequestServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthRequestTypeJson.ADMIN_APPROVAL -> {
|
AuthRequestTypeJson.ADMIN_APPROVAL -> {
|
||||||
authenticatedAuthRequestsApi.createAdminAuthRequest(
|
authenticatedAuthRequestsApi
|
||||||
deviceIdentifier = deviceId,
|
.createAdminAuthRequest(
|
||||||
body = AuthRequestRequestJson(
|
deviceIdentifier = deviceId,
|
||||||
email = email,
|
body = AuthRequestRequestJson(
|
||||||
publicKey = publicKey,
|
email = email,
|
||||||
deviceId = deviceId,
|
publicKey = publicKey,
|
||||||
accessCode = accessCode,
|
deviceId = deviceId,
|
||||||
fingerprint = fingerprint,
|
accessCode = accessCode,
|
||||||
type = authRequestType,
|
fingerprint = fingerprint,
|
||||||
),
|
type = authRequestType,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +68,15 @@ class NewAuthRequestServiceImpl(
|
||||||
isSso: Boolean,
|
isSso: Boolean,
|
||||||
): Result<AuthRequestsResponseJson.AuthRequest> =
|
): Result<AuthRequestsResponseJson.AuthRequest> =
|
||||||
if (isSso) {
|
if (isSso) {
|
||||||
authenticatedAuthRequestsApi.getAuthRequest(requestId)
|
authenticatedAuthRequestsApi
|
||||||
|
.getAuthRequest(requestId = requestId)
|
||||||
|
.toResult()
|
||||||
} else {
|
} else {
|
||||||
unauthenticatedAuthRequestsApi.getAuthRequestUpdate(
|
unauthenticatedAuthRequestsApi
|
||||||
requestId = requestId,
|
.getAuthRequestUpdate(
|
||||||
accessCode = accessCode,
|
requestId = requestId,
|
||||||
)
|
accessCode = accessCode,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysRe
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of [OrganizationService].
|
* Default implementation of [OrganizationService].
|
||||||
|
@ -31,6 +32,7 @@ class OrganizationServiceImpl(
|
||||||
resetPasswordKey = resetPasswordKey,
|
resetPasswordKey = resetPasswordKey,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getOrganizationDomainSsoDetails(
|
override suspend fun getOrganizationDomainSsoDetails(
|
||||||
email: String,
|
email: String,
|
||||||
|
@ -40,6 +42,7 @@ class OrganizationServiceImpl(
|
||||||
email = email,
|
email = email,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getOrganizationAutoEnrollStatus(
|
override suspend fun getOrganizationAutoEnrollStatus(
|
||||||
organizationIdentifier: String,
|
organizationIdentifier: String,
|
||||||
|
@ -47,6 +50,7 @@ class OrganizationServiceImpl(
|
||||||
.getOrganizationAutoEnrollResponse(
|
.getOrganizationAutoEnrollResponse(
|
||||||
organizationIdentifier = organizationIdentifier,
|
organizationIdentifier = organizationIdentifier,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getOrganizationKeys(
|
override suspend fun getOrganizationKeys(
|
||||||
organizationId: String,
|
organizationId: String,
|
||||||
|
@ -54,6 +58,7 @@ class OrganizationServiceImpl(
|
||||||
.getOrganizationKeys(
|
.getOrganizationKeys(
|
||||||
organizationId = organizationId,
|
organizationId = organizationId,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getVerifiedOrganizationDomainSsoDetails(
|
override suspend fun getVerifiedOrganizationDomainSsoDetails(
|
||||||
email: String,
|
email: String,
|
||||||
|
@ -63,4 +68,5 @@ class OrganizationServiceImpl(
|
||||||
email = email,
|
email = email,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import com.x8bit.bitwarden.data.auth.manager.model.CreateAuthRequestResult
|
||||||
import com.x8bit.bitwarden.data.auth.manager.util.isSso
|
import com.x8bit.bitwarden.data.auth.manager.util.isSso
|
||||||
import com.x8bit.bitwarden.data.auth.manager.util.toAuthRequestTypeJson
|
import com.x8bit.bitwarden.data.auth.manager.util.toAuthRequestTypeJson
|
||||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||||
import com.x8bit.bitwarden.data.platform.util.flatMap
|
import com.x8bit.bitwarden.data.platform.util.flatMap
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
@ -65,7 +66,7 @@ class AuthRequestManagerImpl(
|
||||||
email: String,
|
email: String,
|
||||||
authRequestType: AuthRequestType,
|
authRequestType: AuthRequestType,
|
||||||
): Flow<CreateAuthRequestResult> = flow {
|
): Flow<CreateAuthRequestResult> = flow {
|
||||||
val initialResult = createNewAuthRequest(
|
val initialResult = createNewAuthRequestIfNecessary(
|
||||||
email = email,
|
email = email,
|
||||||
authRequestType = authRequestType.toAuthRequestTypeJson(),
|
authRequestType = authRequestType.toAuthRequestTypeJson(),
|
||||||
)
|
)
|
||||||
|
@ -74,7 +75,6 @@ class AuthRequestManagerImpl(
|
||||||
emit(CreateAuthRequestResult.Error)
|
emit(CreateAuthRequestResult.Error)
|
||||||
return@flow
|
return@flow
|
||||||
}
|
}
|
||||||
val authRequestResponse = initialResult.authRequestResponse
|
|
||||||
var authRequest = initialResult.authRequest
|
var authRequest = initialResult.authRequest
|
||||||
emit(CreateAuthRequestResult.Update(authRequest))
|
emit(CreateAuthRequestResult.Update(authRequest))
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ class AuthRequestManagerImpl(
|
||||||
newAuthRequestService
|
newAuthRequestService
|
||||||
.getAuthRequestUpdate(
|
.getAuthRequestUpdate(
|
||||||
requestId = authRequest.id,
|
requestId = authRequest.id,
|
||||||
accessCode = authRequestResponse.accessCode,
|
accessCode = initialResult.accessCode,
|
||||||
isSso = authRequestType.isSso,
|
isSso = authRequestType.isSso,
|
||||||
)
|
)
|
||||||
.map { request ->
|
.map { request ->
|
||||||
|
@ -112,7 +112,8 @@ class AuthRequestManagerImpl(
|
||||||
emit(
|
emit(
|
||||||
CreateAuthRequestResult.Success(
|
CreateAuthRequestResult.Success(
|
||||||
authRequest = updateAuthRequest,
|
authRequest = updateAuthRequest,
|
||||||
authRequestResponse = authRequestResponse,
|
privateKey = initialResult.privateKey,
|
||||||
|
accessCode = initialResult.accessCode,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -354,6 +355,52 @@ class AuthRequestManagerImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new auth request for the given email and returns a [NewAuthRequestData].
|
||||||
|
* If the auth request type is [AuthRequestTypeJson.ADMIN_APPROVAL], check for a
|
||||||
|
* pending auth request and return it if it exists we should return that request.
|
||||||
|
*/
|
||||||
|
private suspend fun createNewAuthRequestIfNecessary(
|
||||||
|
email: String,
|
||||||
|
authRequestType: AuthRequestTypeJson,
|
||||||
|
): Result<NewAuthRequestData> {
|
||||||
|
return if (authRequestType == AuthRequestTypeJson.ADMIN_APPROVAL) {
|
||||||
|
authDiskSource
|
||||||
|
.getPendingAuthRequest(requireNotNull(activeUserId))
|
||||||
|
?.let { pendingAuthRequest ->
|
||||||
|
authRequestsService
|
||||||
|
.getAuthRequest(pendingAuthRequest.requestId)
|
||||||
|
.map {
|
||||||
|
NewAuthRequestData(
|
||||||
|
authRequest = AuthRequest(
|
||||||
|
id = it.id,
|
||||||
|
publicKey = it.publicKey,
|
||||||
|
platform = it.platform,
|
||||||
|
ipAddress = it.ipAddress,
|
||||||
|
key = it.key,
|
||||||
|
masterPasswordHash = it.masterPasswordHash,
|
||||||
|
creationDate = it.creationDate,
|
||||||
|
responseDate = it.responseDate,
|
||||||
|
requestApproved = it.requestApproved ?: false,
|
||||||
|
originUrl = it.originUrl,
|
||||||
|
fingerprint = pendingAuthRequest.requestFingerprint,
|
||||||
|
),
|
||||||
|
privateKey = pendingAuthRequest.requestPrivateKey,
|
||||||
|
accessCode = pendingAuthRequest.requestAccessCode,
|
||||||
|
)
|
||||||
|
.asSuccess()
|
||||||
|
}
|
||||||
|
.getOrNull()
|
||||||
|
}
|
||||||
|
?: createNewAuthRequest(email = email, authRequestType = authRequestType)
|
||||||
|
} else {
|
||||||
|
createNewAuthRequest(
|
||||||
|
email = email,
|
||||||
|
authRequestType = authRequestType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to create a new auth request for the given email and returns a [NewAuthRequestData]
|
* Attempts to create a new auth request for the given email and returns a [NewAuthRequestData]
|
||||||
* with the [AuthRequest] and [AuthRequestResponse].
|
* with the [AuthRequest] and [AuthRequestResponse].
|
||||||
|
@ -381,6 +428,8 @@ class AuthRequestManagerImpl(
|
||||||
pendingAuthRequest = PendingAuthRequestJson(
|
pendingAuthRequest = PendingAuthRequestJson(
|
||||||
requestId = it.id,
|
requestId = it.id,
|
||||||
requestPrivateKey = authRequestResponse.privateKey,
|
requestPrivateKey = authRequestResponse.privateKey,
|
||||||
|
requestAccessCode = authRequestResponse.accessCode,
|
||||||
|
requestFingerprint = authRequestResponse.fingerprint,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -400,7 +449,13 @@ class AuthRequestManagerImpl(
|
||||||
fingerprint = authRequestResponse.fingerprint,
|
fingerprint = authRequestResponse.fingerprint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.map { NewAuthRequestData(it, authRequestResponse) }
|
.map {
|
||||||
|
NewAuthRequestData(
|
||||||
|
authRequest = it,
|
||||||
|
privateKey = authRequestResponse.privateKey,
|
||||||
|
accessCode = authRequestResponse.accessCode,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getFingerprintPhrase(
|
private suspend fun getFingerprintPhrase(
|
||||||
|
@ -420,5 +475,6 @@ class AuthRequestManagerImpl(
|
||||||
*/
|
*/
|
||||||
private data class NewAuthRequestData(
|
private data class NewAuthRequestData(
|
||||||
val authRequest: AuthRequest,
|
val authRequest: AuthRequest,
|
||||||
val authRequestResponse: AuthRequestResponse,
|
val privateKey: String,
|
||||||
|
val accessCode: String,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.x8bit.bitwarden.data.auth.manager.model
|
package com.x8bit.bitwarden.data.auth.manager.model
|
||||||
|
|
||||||
import com.bitwarden.core.AuthRequestResponse
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Models result of creating a new login approval request.
|
* Models result of creating a new login approval request.
|
||||||
*/
|
*/
|
||||||
|
@ -18,7 +16,8 @@ sealed class CreateAuthRequestResult {
|
||||||
*/
|
*/
|
||||||
data class Success(
|
data class Success(
|
||||||
val authRequest: AuthRequest,
|
val authRequest: AuthRequest,
|
||||||
val authRequestResponse: AuthRequestResponse,
|
val privateKey: String,
|
||||||
|
val accessCode: String,
|
||||||
) : CreateAuthRequestResult()
|
) : CreateAuthRequestResult()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.auth.repository
|
||||||
|
|
||||||
import com.bitwarden.core.AuthRequestMethod
|
import com.bitwarden.core.AuthRequestMethod
|
||||||
import com.bitwarden.core.InitUserCryptoMethod
|
import com.bitwarden.core.InitUserCryptoMethod
|
||||||
import com.bitwarden.core.InitUserCryptoRequest
|
|
||||||
import com.bitwarden.crypto.HashPurpose
|
import com.bitwarden.crypto.HashPurpose
|
||||||
import com.bitwarden.crypto.Kdf
|
import com.bitwarden.crypto.Kdf
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
|
@ -23,6 +22,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJs
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||||
|
@ -114,7 +114,6 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.OrganizationType
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.InitializeCryptoResult
|
|
||||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
|
||||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockError
|
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockError
|
||||||
|
@ -1249,41 +1248,17 @@ class AuthRepositoryImpl(
|
||||||
?.activeAccount
|
?.activeAccount
|
||||||
?.profile
|
?.profile
|
||||||
?: return ValidatePinResult.Error
|
?: return ValidatePinResult.Error
|
||||||
val privateKey = authDiskSource
|
|
||||||
.getPrivateKey(userId = activeAccount.userId)
|
|
||||||
?: return ValidatePinResult.Error
|
|
||||||
val pinProtectedUserKey = authDiskSource
|
val pinProtectedUserKey = authDiskSource
|
||||||
.getPinProtectedUserKey(userId = activeAccount.userId)
|
.getPinProtectedUserKey(userId = activeAccount.userId)
|
||||||
?: return ValidatePinResult.Error
|
?: return ValidatePinResult.Error
|
||||||
|
|
||||||
// HACK: As the SDK doesn't provide a way to directly validate the pin yet, we instead
|
|
||||||
// try to initialize the user crypto, and if it succeeds then the PIN is correct, otherwise
|
|
||||||
// the PIN is incorrect.
|
|
||||||
return vaultSdkSource
|
return vaultSdkSource
|
||||||
.initializeCrypto(
|
.validatePin(
|
||||||
userId = activeAccount.userId,
|
userId = activeAccount.userId,
|
||||||
request = InitUserCryptoRequest(
|
pin = pin,
|
||||||
kdfParams = activeAccount.toSdkParams(),
|
pinProtectedUserKey = pinProtectedUserKey,
|
||||||
email = activeAccount.email,
|
|
||||||
privateKey = privateKey,
|
|
||||||
method = InitUserCryptoMethod.Pin(
|
|
||||||
pin = pin,
|
|
||||||
pinProtectedUserKey = pinProtectedUserKey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.fold(
|
.fold(
|
||||||
onSuccess = {
|
onSuccess = { ValidatePinResult.Success(isValid = it) },
|
||||||
when (it) {
|
|
||||||
InitializeCryptoResult.Success -> {
|
|
||||||
ValidatePinResult.Success(isValid = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is InitializeCryptoResult.AuthenticationError -> {
|
|
||||||
ValidatePinResult.Success(isValid = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFailure = { ValidatePinResult.Error },
|
onFailure = { ValidatePinResult.Error },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1302,13 +1277,21 @@ class AuthRepositoryImpl(
|
||||||
.sendVerificationEmail(
|
.sendVerificationEmail(
|
||||||
SendVerificationEmailRequestJson(
|
SendVerificationEmailRequestJson(
|
||||||
email = email,
|
email = email,
|
||||||
name = name,
|
name = name.takeUnless { it.isBlank() },
|
||||||
receiveMarketingEmails = receiveMarketingEmails,
|
receiveMarketingEmails = receiveMarketingEmails,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.fold(
|
.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
SendVerificationEmailResult.Success(it)
|
when (it) {
|
||||||
|
is SendVerificationEmailResponseJson.Invalid -> {
|
||||||
|
SendVerificationEmailResult.Error(it.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
is SendVerificationEmailResponseJson.Success -> {
|
||||||
|
SendVerificationEmailResult.Success(it.emailVerificationToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
SendVerificationEmailResult.Error(null)
|
SendVerificationEmailResult.Error(null)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.repository.util
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.JwtTokenDataJson
|
import com.x8bit.bitwarden.data.auth.repository.model.JwtTokenDataJson
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlDecodeOrNull
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal, generally basic [Json] instance for JWT parsing purposes.
|
* Internal, generally basic [Json] instance for JWT parsing purposes.
|
||||||
|
@ -17,17 +18,24 @@ private val json: Json by lazy {
|
||||||
/**
|
/**
|
||||||
* Parses a [JwtTokenDataJson] from the given [jwtToken], or `null` if this parsing is not possible.
|
* Parses a [JwtTokenDataJson] from the given [jwtToken], or `null` if this parsing is not possible.
|
||||||
*/
|
*/
|
||||||
@Suppress("MagicNumber")
|
@Suppress("MagicNumber", "TooGenericExceptionCaught")
|
||||||
fun parseJwtTokenDataOrNull(jwtToken: String): JwtTokenDataJson? {
|
fun parseJwtTokenDataOrNull(jwtToken: String): JwtTokenDataJson? {
|
||||||
val parts = jwtToken.split(".")
|
val parts = jwtToken.split(".")
|
||||||
if (parts.size != 3) return null
|
if (parts.size != 3) {
|
||||||
|
Timber.e(IllegalArgumentException("Incorrect number of parts"), "Invalid JWT Token")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val dataJson = parts[1]
|
val dataJson = parts[1]
|
||||||
val decodedDataJson = dataJson.base64UrlDecodeOrNull() ?: return null
|
val decodedDataJson = dataJson.base64UrlDecodeOrNull() ?: run {
|
||||||
|
Timber.e(IllegalArgumentException("Unable to decode"), "Invalid JWT Token")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
json.decodeFromString<JwtTokenDataJson>(decodedDataJson)
|
json.decodeFromString<JwtTokenDataJson>(decodedDataJson)
|
||||||
} catch (_: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
|
Timber.e(throwable, "Failed to decode JwtTokenDataJson")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,7 @@ class BitwardenAccessibilityService : AccessibilityService() {
|
||||||
lateinit var processor: BitwardenAccessibilityProcessor
|
lateinit var processor: BitwardenAccessibilityProcessor
|
||||||
|
|
||||||
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
override fun onAccessibilityEvent(event: AccessibilityEvent) {
|
||||||
if (rootInActiveWindow?.packageName != event.packageName) return
|
processor.processAccessibilityEvent(event = event) { rootInActiveWindow }
|
||||||
processor.processAccessibilityEvent(rootAccessibilityNodeInfo = event.source)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInterrupt() = Unit
|
override fun onInterrupt() = Unit
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.autofill.accessibility.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
import android.view.accessibility.AccessibilityManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
||||||
|
@ -55,8 +56,12 @@ object AccessibilityModule {
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun providesAccessibilityEnabledManager(): AccessibilityEnabledManager =
|
fun providesAccessibilityEnabledManager(
|
||||||
AccessibilityEnabledManagerImpl()
|
accessibilityManager: AccessibilityManager,
|
||||||
|
): AccessibilityEnabledManager =
|
||||||
|
AccessibilityEnabledManagerImpl(
|
||||||
|
accessibilityManager = accessibilityManager,
|
||||||
|
)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
|
@ -110,6 +115,12 @@ object AccessibilityModule {
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
): PackageManager = context.packageManager
|
): PackageManager = context.packageManager
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun provideAccessibilityManager(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
): AccessibilityManager = context.getSystemService(AccessibilityManager::class.java)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun providesPowerManager(
|
fun providesPowerManager(
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManager
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityActivityManagerImpl
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityEnabledManager
|
|
||||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.android.scopes.ActivityScoped
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides dependencies within the accessibility package scoped to the activity.
|
|
||||||
*/
|
|
||||||
@Module
|
|
||||||
@InstallIn(ActivityComponent::class)
|
|
||||||
object ActivityAccessibilityModule {
|
|
||||||
@ActivityScoped
|
|
||||||
@Provides
|
|
||||||
fun providesAccessibilityActivityManager(
|
|
||||||
@ApplicationContext context: Context,
|
|
||||||
accessibilityEnabledManager: AccessibilityEnabledManager,
|
|
||||||
appStateManager: AppStateManager,
|
|
||||||
lifecycleScope: LifecycleCoroutineScope,
|
|
||||||
): AccessibilityActivityManager =
|
|
||||||
AccessibilityActivityManagerImpl(
|
|
||||||
context = context,
|
|
||||||
accessibilityEnabledManager = accessibilityEnabledManager,
|
|
||||||
appStateManager = appStateManager,
|
|
||||||
lifecycleScope = lifecycleScope,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper for dealing with accessibility configuration that must be scoped to a specific
|
|
||||||
* [Activity]. In particular, this should be injected into an [Activity] to ensure that the
|
|
||||||
* [AccessibilityEnabledManager] reports correct values.
|
|
||||||
*/
|
|
||||||
interface AccessibilityActivityManager
|
|
|
@ -1,28 +0,0 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.util.isAccessibilityServiceEnabled
|
|
||||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default implementation of the [AccessibilityActivityManager].
|
|
||||||
*/
|
|
||||||
class AccessibilityActivityManagerImpl(
|
|
||||||
private val context: Context,
|
|
||||||
private val accessibilityEnabledManager: AccessibilityEnabledManager,
|
|
||||||
appStateManager: AppStateManager,
|
|
||||||
lifecycleScope: LifecycleCoroutineScope,
|
|
||||||
) : AccessibilityActivityManager {
|
|
||||||
init {
|
|
||||||
appStateManager
|
|
||||||
.appForegroundStateFlow
|
|
||||||
.onEach {
|
|
||||||
accessibilityEnabledManager.isAccessibilityEnabled =
|
|
||||||
context.isAccessibilityServiceEnabled
|
|
||||||
}
|
|
||||||
.launchIn(lifecycleScope)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,15 +7,7 @@ import kotlinx.coroutines.flow.StateFlow
|
||||||
*/
|
*/
|
||||||
interface AccessibilityEnabledManager {
|
interface AccessibilityEnabledManager {
|
||||||
/**
|
/**
|
||||||
* Whether or not the accessibility service should be considered enabled.
|
* Emits updates that track whether the accessibility autofill service is enabled..
|
||||||
*
|
|
||||||
* Note that changing this does not enable or disable autofill; it is only an indicator that
|
|
||||||
* this has occurred elsewhere.
|
|
||||||
*/
|
|
||||||
var isAccessibilityEnabled: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Emits updates that track [isAccessibilityEnabled] values.
|
|
||||||
*/
|
*/
|
||||||
val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import android.view.accessibility.AccessibilityManager
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
@ -7,14 +8,18 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||||
/**
|
/**
|
||||||
* The default implementation of [AccessibilityEnabledManager].
|
* The default implementation of [AccessibilityEnabledManager].
|
||||||
*/
|
*/
|
||||||
class AccessibilityEnabledManagerImpl : AccessibilityEnabledManager {
|
class AccessibilityEnabledManagerImpl(
|
||||||
|
accessibilityManager: AccessibilityManager,
|
||||||
|
) : AccessibilityEnabledManager {
|
||||||
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(value = false)
|
private val mutableIsAccessibilityEnabledStateFlow = MutableStateFlow(value = false)
|
||||||
|
|
||||||
override var isAccessibilityEnabled: Boolean
|
init {
|
||||||
get() = mutableIsAccessibilityEnabledStateFlow.value
|
accessibilityManager.addAccessibilityStateChangeListener(
|
||||||
set(value) {
|
AccessibilityManager.AccessibilityStateChangeListener { isEnabled ->
|
||||||
mutableIsAccessibilityEnabledStateFlow.value = value
|
mutableIsAccessibilityEnabledStateFlow.value = isEnabled
|
||||||
}
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
override val isAccessibilityEnabledStateFlow: StateFlow<Boolean>
|
||||||
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
|
get() = mutableIsAccessibilityEnabledStateFlow.asStateFlow()
|
||||||
|
|
|
@ -8,4 +8,6 @@ import android.view.accessibility.AccessibilityNodeInfo
|
||||||
data class FillableFields(
|
data class FillableFields(
|
||||||
val usernameField: AccessibilityNodeInfo?,
|
val usernameField: AccessibilityNodeInfo?,
|
||||||
val passwordFields: List<AccessibilityNodeInfo>,
|
val passwordFields: List<AccessibilityNodeInfo>,
|
||||||
)
|
) {
|
||||||
|
val hasFields: Boolean = usernameField != null || passwordFields.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.processor
|
package com.x8bit.bitwarden.data.autofill.accessibility.processor
|
||||||
|
|
||||||
|
import android.view.accessibility.AccessibilityEvent
|
||||||
import android.view.accessibility.AccessibilityNodeInfo
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +8,12 @@ import android.view.accessibility.AccessibilityNodeInfo
|
||||||
*/
|
*/
|
||||||
interface BitwardenAccessibilityProcessor {
|
interface BitwardenAccessibilityProcessor {
|
||||||
/**
|
/**
|
||||||
* Processes the [AccessibilityNodeInfo] for autofill options.
|
* Processes the [AccessibilityEvent] for autofill options and grant access to the current
|
||||||
|
* [AccessibilityNodeInfo] via the [rootAccessibilityNodeInfoProvider] (note that calling the
|
||||||
|
* `rootAccessibilityNodeInfoProvider` is expensive).
|
||||||
*/
|
*/
|
||||||
fun processAccessibilityEvent(rootAccessibilityNodeInfo: AccessibilityNodeInfo?)
|
fun processAccessibilityEvent(
|
||||||
|
event: AccessibilityEvent,
|
||||||
|
rootAccessibilityNodeInfoProvider: () -> AccessibilityNodeInfo?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.autofill.accessibility.processor
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
|
import android.view.accessibility.AccessibilityEvent
|
||||||
import android.view.accessibility.AccessibilityNodeInfo
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
@ -25,37 +26,48 @@ class BitwardenAccessibilityProcessorImpl(
|
||||||
private val launcherPackageNameManager: LauncherPackageNameManager,
|
private val launcherPackageNameManager: LauncherPackageNameManager,
|
||||||
private val powerManager: PowerManager,
|
private val powerManager: PowerManager,
|
||||||
) : BitwardenAccessibilityProcessor {
|
) : BitwardenAccessibilityProcessor {
|
||||||
override fun processAccessibilityEvent(rootAccessibilityNodeInfo: AccessibilityNodeInfo?) {
|
override fun processAccessibilityEvent(
|
||||||
val rootNode = rootAccessibilityNodeInfo ?: return
|
event: AccessibilityEvent,
|
||||||
|
rootAccessibilityNodeInfoProvider: () -> AccessibilityNodeInfo?,
|
||||||
|
) {
|
||||||
|
val eventNode = event.source ?: return
|
||||||
// Ignore the event when the phone is inactive
|
// Ignore the event when the phone is inactive
|
||||||
if (!powerManager.isInteractive) return
|
if (!powerManager.isInteractive) return
|
||||||
// We skip if the system package
|
// We skip if the system package
|
||||||
if (rootNode.isSystemPackage) return
|
if (eventNode.isSystemPackage) return
|
||||||
// We skip any package that is a launcher or unsupported
|
// We skip any package that is unsupported
|
||||||
if (rootNode.shouldSkipPackage ||
|
if (eventNode.shouldSkipPackage) return
|
||||||
launcherPackageNameManager.launcherPackages.any { it == rootNode.packageName }
|
// We skip any package that is a launcher
|
||||||
) {
|
if (launcherPackageNameManager.launcherPackages.any { it == eventNode.packageName }) {
|
||||||
// Clear the action since this event needs to be ignored completely
|
|
||||||
accessibilityAutofillManager.accessibilityAction = null
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process the event if the tile was clicked
|
// Only process the event if the tile was clicked
|
||||||
val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return
|
val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return
|
||||||
|
// We only call for the root node once after all other checks
|
||||||
|
// have passed because it is significant performance hit
|
||||||
|
if (rootAccessibilityNodeInfoProvider()?.packageName != event.packageName) return
|
||||||
|
|
||||||
|
// Clear the action since we are now acting on it
|
||||||
accessibilityAutofillManager.accessibilityAction = null
|
accessibilityAutofillManager.accessibilityAction = null
|
||||||
|
|
||||||
when (accessibilityAction) {
|
when (accessibilityAction) {
|
||||||
is AccessibilityAction.AttemptFill -> {
|
is AccessibilityAction.AttemptFill -> {
|
||||||
handleAttemptFill(rootNode = rootNode, attemptFill = accessibilityAction)
|
handleAttemptFill(rootNode = eventNode, attemptFill = accessibilityAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessibilityAction.AttemptParseUri -> handleAttemptParseUri(rootNode = rootNode)
|
AccessibilityAction.AttemptParseUri -> handleAttemptParseUri(rootNode = eventNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAttemptParseUri(rootNode: AccessibilityNodeInfo) {
|
private fun handleAttemptParseUri(rootNode: AccessibilityNodeInfo) {
|
||||||
accessibilityParser
|
accessibilityParser
|
||||||
.parseForUriOrPackageName(rootNode = rootNode)
|
.parseForUriOrPackageName(rootNode = rootNode)
|
||||||
|
?.takeIf {
|
||||||
|
accessibilityParser
|
||||||
|
.parseForFillableFields(rootNode = rootNode, uri = it)
|
||||||
|
.hasFields
|
||||||
|
}
|
||||||
?.let { uri ->
|
?.let { uri ->
|
||||||
context.startActivity(
|
context.startActivity(
|
||||||
createAutofillSelectionIntent(
|
createAutofillSelectionIntent(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api
|
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Url
|
import retrofit2.http.Url
|
||||||
|
|
||||||
|
@ -15,5 +16,5 @@ interface DigitalAssetLinkApi {
|
||||||
@GET
|
@GET
|
||||||
suspend fun getDigitalAssetLinks(
|
suspend fun getDigitalAssetLinks(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
): Result<List<DigitalAssetLinkResponseJson>>
|
): NetworkResult<List<DigitalAssetLinkResponseJson>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api.DigitalAssetLinkApi
|
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api.DigitalAssetLinkApi
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Primary implementation of [DigitalAssetLinkService].
|
* Primary implementation of [DigitalAssetLinkService].
|
||||||
|
@ -18,4 +19,5 @@ class DigitalAssetLinkServiceImpl(
|
||||||
.getDigitalAssetLinks(
|
.getDigitalAssetLinks(
|
||||||
url = "$scheme$relyingParty/.well-known/assetlinks.json",
|
url = "$scheme$relyingParty/.well-known/assetlinks.json",
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,11 @@ interface SettingsDiskSource {
|
||||||
*/
|
*/
|
||||||
var lastDatabaseSchemeChangeInstant: Instant?
|
var lastDatabaseSchemeChangeInstant: Instant?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits updates that track [lastDatabaseSchemeChangeInstant].
|
||||||
|
*/
|
||||||
|
val lastDatabaseSchemeChangeInstantFlow: Flow<Instant?>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all the settings data for the given user.
|
* Clears all the settings data for the given user.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -75,6 +75,8 @@ class SettingsDiskSourceImpl(
|
||||||
|
|
||||||
private val mutableHasUserLoggedInOrCreatedAccountFlow = bufferedMutableSharedFlow<Boolean?>()
|
private val mutableHasUserLoggedInOrCreatedAccountFlow = bufferedMutableSharedFlow<Boolean?>()
|
||||||
|
|
||||||
|
private val mutableLastDatabaseSchemeChangeInstantFlow = bufferedMutableSharedFlow<Instant?>()
|
||||||
|
|
||||||
private val mutableScreenCaptureAllowedFlowMap =
|
private val mutableScreenCaptureAllowedFlowMap =
|
||||||
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
|
||||||
|
|
||||||
|
@ -158,7 +160,14 @@ class SettingsDiskSourceImpl(
|
||||||
|
|
||||||
override var lastDatabaseSchemeChangeInstant: Instant?
|
override var lastDatabaseSchemeChangeInstant: Instant?
|
||||||
get() = getLong(LAST_SCHEME_CHANGE_INSTANT)?.let { Instant.ofEpochMilli(it) }
|
get() = getLong(LAST_SCHEME_CHANGE_INSTANT)?.let { Instant.ofEpochMilli(it) }
|
||||||
set(value) = putLong(LAST_SCHEME_CHANGE_INSTANT, value?.toEpochMilli())
|
set(value) {
|
||||||
|
putLong(LAST_SCHEME_CHANGE_INSTANT, value?.toEpochMilli())
|
||||||
|
mutableLastDatabaseSchemeChangeInstantFlow.tryEmit(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val lastDatabaseSchemeChangeInstantFlow: Flow<Instant?>
|
||||||
|
get() = mutableLastDatabaseSchemeChangeInstantFlow
|
||||||
|
.onSubscription { emit(lastDatabaseSchemeChangeInstant) }
|
||||||
|
|
||||||
override fun clearData(userId: String) {
|
override fun clearData(userId: String) {
|
||||||
storeVaultTimeoutInMinutes(userId = userId, vaultTimeoutInMinutes = null)
|
storeVaultTimeoutInMinutes(userId = userId, vaultTimeoutInMinutes = null)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,5 +10,5 @@ import retrofit2.http.GET
|
||||||
interface ConfigApi {
|
interface ConfigApi {
|
||||||
|
|
||||||
@GET("config")
|
@GET("config")
|
||||||
suspend fun getConfig(): Result<ConfigResponseJson>
|
suspend fun getConfig(): NetworkResult<ConfigResponseJson>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
@ -9,5 +10,7 @@ import retrofit2.http.POST
|
||||||
*/
|
*/
|
||||||
interface EventApi {
|
interface EventApi {
|
||||||
@POST("/collect")
|
@POST("/collect")
|
||||||
suspend fun collectOrganizationEvents(@Body events: List<OrganizationEventJson>): Result<Unit>
|
suspend fun collectOrganizationEvents(
|
||||||
|
@Body events: List<OrganizationEventJson>,
|
||||||
|
): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
package com.x8bit.bitwarden.data.platform.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.PUT
|
import retrofit2.http.PUT
|
||||||
|
@ -13,5 +14,5 @@ interface PushApi {
|
||||||
suspend fun putDeviceToken(
|
suspend fun putDeviceToken(
|
||||||
@Path("appId") appId: String,
|
@Path("appId") appId: String,
|
||||||
@Body body: PushTokenRequest,
|
@Body body: PushTokenRequest,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.core
|
package com.x8bit.bitwarden.data.platform.datasource.network.core
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
import okio.Timeout
|
import okio.Timeout
|
||||||
|
@ -18,33 +17,36 @@ import java.lang.reflect.Type
|
||||||
private const val NO_CONTENT_RESPONSE_CODE: Int = 204
|
private const val NO_CONTENT_RESPONSE_CODE: Int = 204
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [Call] for wrapping a network request into a [Result].
|
* A [Call] for wrapping a network request into a [NetworkResult].
|
||||||
*/
|
*/
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
class ResultCall<T>(
|
class NetworkResultCall<T>(
|
||||||
private val backingCall: Call<T>,
|
private val backingCall: Call<T>,
|
||||||
private val successType: Type,
|
private val successType: Type,
|
||||||
) : Call<Result<T>> {
|
) : Call<NetworkResult<T>> {
|
||||||
override fun cancel(): Unit = backingCall.cancel()
|
override fun cancel(): Unit = backingCall.cancel()
|
||||||
|
|
||||||
override fun clone(): Call<Result<T>> = ResultCall(backingCall, successType)
|
override fun clone(): Call<NetworkResult<T>> = NetworkResultCall(backingCall, successType)
|
||||||
|
|
||||||
override fun enqueue(callback: Callback<Result<T>>): Unit = backingCall.enqueue(
|
override fun enqueue(callback: Callback<NetworkResult<T>>): Unit = backingCall.enqueue(
|
||||||
object : Callback<T> {
|
object : Callback<T> {
|
||||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
override fun onResponse(call: Call<T>, response: Response<T>) {
|
||||||
callback.onResponse(this@ResultCall, Response.success(response.toResult()))
|
callback.onResponse(
|
||||||
|
this@NetworkResultCall,
|
||||||
|
Response.success(response.toNetworkResult()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
override fun onFailure(call: Call<T>, t: Throwable) {
|
||||||
callback.onResponse(this@ResultCall, Response.success(t.toFailure()))
|
callback.onResponse(this@NetworkResultCall, Response.success(t.toFailure()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("TooGenericExceptionCaught")
|
@Suppress("TooGenericExceptionCaught")
|
||||||
override fun execute(): Response<Result<T>> =
|
override fun execute(): Response<NetworkResult<T>> =
|
||||||
try {
|
try {
|
||||||
Response.success(backingCall.execute().toResult())
|
Response.success(backingCall.execute().toNetworkResult())
|
||||||
} catch (ioException: IOException) {
|
} catch (ioException: IOException) {
|
||||||
Response.success(ioException.toFailure())
|
Response.success(ioException.toFailure())
|
||||||
} catch (runtimeException: RuntimeException) {
|
} catch (runtimeException: RuntimeException) {
|
||||||
|
@ -60,20 +62,18 @@ class ResultCall<T>(
|
||||||
override fun timeout(): Timeout = backingCall.timeout()
|
override fun timeout(): Timeout = backingCall.timeout()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously send the request and return its response as a [Result].
|
* Synchronously send the request and return its response as a [NetworkResult].
|
||||||
*/
|
*/
|
||||||
fun executeForResult(): Result<T> = requireNotNull(execute().body())
|
fun executeForResult(): NetworkResult<T> = requireNotNull(execute().body())
|
||||||
|
|
||||||
private fun Throwable.toFailure(): Result<T> =
|
private fun Throwable.toFailure(): NetworkResult<T> {
|
||||||
this
|
// We rebuild the URL without query params, we do not want to log those
|
||||||
.also {
|
val url = backingCall.request().url.toUrl().run { "$protocol://$authority$path" }
|
||||||
// We rebuild the URL without query params, we do not want to log those
|
Timber.w(this, "Network Error: $url")
|
||||||
val url = backingCall.request().url.toUrl().run { "$protocol://$authority$path" }
|
return NetworkResult.Failure(this)
|
||||||
Timber.w(it, "Network Error: $url")
|
}
|
||||||
}
|
|
||||||
.asFailure()
|
|
||||||
|
|
||||||
private fun Response<T>.toResult(): Result<T> =
|
private fun Response<T>.toNetworkResult(): NetworkResult<T> =
|
||||||
if (!this.isSuccessful) {
|
if (!this.isSuccessful) {
|
||||||
HttpException(this).toFailure()
|
HttpException(this).toFailure()
|
||||||
} else {
|
} else {
|
||||||
|
@ -81,11 +81,11 @@ class ResultCall<T>(
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
when {
|
when {
|
||||||
// We got a nonnull T as the body, just return it.
|
// We got a nonnull T as the body, just return it.
|
||||||
body != null -> body.asSuccess()
|
body != null -> NetworkResult.Success(body)
|
||||||
// We expected the body to be null since the successType is Unit, just return Unit.
|
// We expected the body to be null since the successType is Unit, just return Unit.
|
||||||
successType == Unit::class.java -> (Unit as T).asSuccess()
|
successType == Unit::class.java -> NetworkResult.Success(Unit as T)
|
||||||
// We allow null for 204's, just return null.
|
// We allow null for 204's, just return null.
|
||||||
this.code() == NO_CONTENT_RESPONSE_CODE -> (null as T).asSuccess()
|
this.code() == NO_CONTENT_RESPONSE_CODE -> NetworkResult.Success(null as T)
|
||||||
// All other null bodies result in an error.
|
// All other null bodies result in an error.
|
||||||
else -> IllegalStateException("Unexpected null body!").toFailure()
|
else -> IllegalStateException("Unexpected null body!").toFailure()
|
||||||
}
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.network.core
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.CallAdapter
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [CallAdapter] for wrapping network requests into [NetworkResult].
|
||||||
|
*/
|
||||||
|
class NetworkResultCallAdapter<T>(
|
||||||
|
private val successType: Type,
|
||||||
|
) : CallAdapter<T, Call<NetworkResult<T>>> {
|
||||||
|
|
||||||
|
override fun responseType(): Type = successType
|
||||||
|
override fun adapt(call: Call<T>): Call<NetworkResult<T>> = NetworkResultCall(call, successType)
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.core
|
package com.x8bit.bitwarden.data.platform.datasource.network.core
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.CallAdapter
|
import retrofit2.CallAdapter
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
@ -7,9 +8,9 @@ import java.lang.reflect.ParameterizedType
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [CallAdapter.Factory] for wrapping network requests into [kotlin.Result].
|
* A [CallAdapter.Factory] for wrapping network requests into [NetworkResult].
|
||||||
*/
|
*/
|
||||||
class ResultCallAdapterFactory : CallAdapter.Factory() {
|
class NetworkResultCallAdapterFactory : CallAdapter.Factory() {
|
||||||
override fun get(
|
override fun get(
|
||||||
returnType: Type,
|
returnType: Type,
|
||||||
annotations: Array<out Annotation>,
|
annotations: Array<out Annotation>,
|
||||||
|
@ -18,13 +19,13 @@ class ResultCallAdapterFactory : CallAdapter.Factory() {
|
||||||
check(returnType is ParameterizedType) { "$returnType must be parameterized" }
|
check(returnType is ParameterizedType) { "$returnType must be parameterized" }
|
||||||
val containerType = getParameterUpperBound(0, returnType)
|
val containerType = getParameterUpperBound(0, returnType)
|
||||||
|
|
||||||
if (getRawType(containerType) != Result::class.java) return null
|
if (getRawType(containerType) != NetworkResult::class.java) return null
|
||||||
check(containerType is ParameterizedType) { "$containerType must be parameterized" }
|
check(containerType is ParameterizedType) { "$containerType must be parameterized" }
|
||||||
|
|
||||||
val requestType = getParameterUpperBound(0, containerType)
|
val requestType = getParameterUpperBound(0, containerType)
|
||||||
|
|
||||||
return if (getRawType(returnType) == Call::class.java) {
|
return if (getRawType(returnType) == Call::class.java) {
|
||||||
ResultCallAdapter<Any>(successType = requestType)
|
NetworkResultCallAdapter<Any>(successType = requestType)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.core
|
|
||||||
|
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.CallAdapter
|
|
||||||
import java.lang.reflect.Type
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A [CallAdapter] for wrapping network requests into [kotlin.Result].
|
|
||||||
*/
|
|
||||||
class ResultCallAdapter<T>(
|
|
||||||
private val successType: Type,
|
|
||||||
) : CallAdapter<T, Call<Result<T>>> {
|
|
||||||
|
|
||||||
override fun responseType(): Type = successType
|
|
||||||
override fun adapt(call: Call<T>): Call<Result<T>> = ResultCall(call, successType)
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.network.model
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper class for a network result for type [T]. If the network request is successful, the
|
||||||
|
* response will be a [Success] containing the data. If the network request is a failure, the
|
||||||
|
* response will be a [Failure] containing the [Throwable].
|
||||||
|
*/
|
||||||
|
@Keep
|
||||||
|
sealed class NetworkResult<out T> {
|
||||||
|
/**
|
||||||
|
* A successful network result with the relevant [T] data.
|
||||||
|
*/
|
||||||
|
data class Success<T>(val value: T) : NetworkResult<T>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A failed network result with the relevant [throwable] error.
|
||||||
|
*/
|
||||||
|
data class Failure(val throwable: Throwable) : NetworkResult<Nothing>()
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.retrofit
|
package com.x8bit.bitwarden.data.platform.datasource.network.retrofit
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
|
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCallAdapterFactory
|
import com.x8bit.bitwarden.data.platform.datasource.network.core.NetworkResultCallAdapterFactory
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptor
|
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptor
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
|
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
|
||||||
|
@ -105,7 +105,7 @@ class RetrofitsImpl(
|
||||||
private val baseRetrofitBuilder: Retrofit.Builder by lazy {
|
private val baseRetrofitBuilder: Retrofit.Builder by lazy {
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||||
.addCallAdapterFactory(ResultCallAdapterFactory())
|
.addCallAdapterFactory(NetworkResultCallAdapterFactory())
|
||||||
.client(baseOkHttpClient)
|
.client(baseOkHttpClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.ConfigApi
|
import com.x8bit.bitwarden.data.platform.datasource.network.api.ConfigApi
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
class ConfigServiceImpl(private val configApi: ConfigApi) : ConfigService {
|
class ConfigServiceImpl(private val configApi: ConfigApi) : ConfigService {
|
||||||
override suspend fun getConfig(): Result<ConfigResponseJson> = configApi.getConfig()
|
override suspend fun getConfig(): Result<ConfigResponseJson> = configApi.getConfig().toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi
|
import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default implementation of the [EventService].
|
* The default implementation of the [EventService].
|
||||||
|
@ -11,5 +12,5 @@ class EventServiceImpl(
|
||||||
) : EventService {
|
) : EventService {
|
||||||
override suspend fun sendOrganizationEvents(
|
override suspend fun sendOrganizationEvents(
|
||||||
events: List<OrganizationEventJson>,
|
events: List<OrganizationEventJson>,
|
||||||
): Result<Unit> = eventApi.collectOrganizationEvents(events = events)
|
): Result<Unit> = eventApi.collectOrganizationEvents(events = events).toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.api.PushApi
|
import com.x8bit.bitwarden.data.platform.datasource.network.api.PushApi
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
|
|
||||||
class PushServiceImpl(
|
class PushServiceImpl(
|
||||||
private val pushApi: PushApi,
|
private val pushApi: PushApi,
|
||||||
|
@ -15,4 +16,5 @@ class PushServiceImpl(
|
||||||
appId = appId,
|
appId = appId,
|
||||||
body = body,
|
body = body,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
package com.x8bit.bitwarden.data.platform.datasource.network.util
|
package com.x8bit.bitwarden.data.platform.datasource.network.util
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCall
|
import com.x8bit.bitwarden.data.platform.datasource.network.core.NetworkResultCall
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously executes the [Call] and returns the [Result].
|
* Synchronously executes the [Call] and returns the [NetworkResult].
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> Call<T>.executeForResult(): Result<T> =
|
inline fun <reified T : Any> Call<T>.executeForNetworkResult(): NetworkResult<T> =
|
||||||
this
|
this
|
||||||
.toResultCall()
|
.toNetworkResultCall()
|
||||||
.executeForResult()
|
.executeForResult()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the existing [Call] in a [ResultCall].
|
* Wraps the existing [Call] in a [NetworkResultCall].
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> Call<T>.toResultCall(): ResultCall<T> =
|
inline fun <reified T : Any> Call<T>.toNetworkResultCall(): NetworkResultCall<T> =
|
||||||
ResultCall(
|
NetworkResultCall(
|
||||||
backingCall = this,
|
backingCall = this,
|
||||||
successType = T::class.java,
|
successType = T::class.java,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.x8bit.bitwarden.data.platform.datasource.network.util
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||||
|
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the [NetworkResult] to a [Result].
|
||||||
|
*/
|
||||||
|
fun <T> NetworkResult<T>.toResult(): Result<T> =
|
||||||
|
when (this) {
|
||||||
|
is NetworkResult.Failure -> this.throwable.asFailure()
|
||||||
|
is NetworkResult.Success -> this.value.asSuccess()
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.platform.manager
|
package com.x8bit.bitwarden.data.platform.manager
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,4 +15,9 @@ interface DatabaseSchemeManager {
|
||||||
* that a scheme change to any database will update this value and trigger a sync.
|
* that a scheme change to any database will update this value and trigger a sync.
|
||||||
*/
|
*/
|
||||||
var lastDatabaseSchemeChangeInstant: Instant?
|
var lastDatabaseSchemeChangeInstant: Instant?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flow of the last database schema change instant.
|
||||||
|
*/
|
||||||
|
val lastDatabaseSchemeChangeInstantFlow: Flow<Instant?>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
package com.x8bit.bitwarden.data.platform.manager
|
package com.x8bit.bitwarden.data.platform.manager
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,10 +12,23 @@ import java.time.Instant
|
||||||
*/
|
*/
|
||||||
class DatabaseSchemeManagerImpl(
|
class DatabaseSchemeManagerImpl(
|
||||||
val settingsDiskSource: SettingsDiskSource,
|
val settingsDiskSource: SettingsDiskSource,
|
||||||
|
val dispatcherManager: DispatcherManager,
|
||||||
) : DatabaseSchemeManager {
|
) : DatabaseSchemeManager {
|
||||||
|
|
||||||
|
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||||
|
|
||||||
override var lastDatabaseSchemeChangeInstant: Instant?
|
override var lastDatabaseSchemeChangeInstant: Instant?
|
||||||
get() = settingsDiskSource.lastDatabaseSchemeChangeInstant
|
get() = settingsDiskSource.lastDatabaseSchemeChangeInstant
|
||||||
set(value) {
|
set(value) {
|
||||||
settingsDiskSource.lastDatabaseSchemeChangeInstant = value
|
settingsDiskSource.lastDatabaseSchemeChangeInstant = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val lastDatabaseSchemeChangeInstantFlow =
|
||||||
|
settingsDiskSource
|
||||||
|
.lastDatabaseSchemeChangeInstantFlow
|
||||||
|
.stateIn(
|
||||||
|
scope = unconfinedScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = settingsDiskSource.lastDatabaseSchemeChangeInstant,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.manager
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
|
import com.x8bit.bitwarden.data.auth.repository.util.activeUserIdChangesFlow
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
|
||||||
|
@ -30,6 +31,7 @@ class FirstTimeActionManagerImpl @Inject constructor(
|
||||||
private val settingsDiskSource: SettingsDiskSource,
|
private val settingsDiskSource: SettingsDiskSource,
|
||||||
private val vaultDiskSource: VaultDiskSource,
|
private val vaultDiskSource: VaultDiskSource,
|
||||||
private val featureFlagManager: FeatureFlagManager,
|
private val featureFlagManager: FeatureFlagManager,
|
||||||
|
private val autofillEnabledManager: AutofillEnabledManager,
|
||||||
) : FirstTimeActionManager {
|
) : FirstTimeActionManager {
|
||||||
|
|
||||||
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
private val unconfinedScope = CoroutineScope(dispatcherManager.unconfined)
|
||||||
|
@ -78,7 +80,7 @@ class FirstTimeActionManagerImpl @Inject constructor(
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
// Can be expanded to support multiple autofill settings
|
// Can be expanded to support multiple autofill settings
|
||||||
settingsDiskSource.getShowAutoFillSettingBadgeFlow(userId = it)
|
getShowAutofillSettingBadgeFlowInternal(userId = it)
|
||||||
.map { showAutofillBadge ->
|
.map { showAutofillBadge ->
|
||||||
listOfNotNull(showAutofillBadge)
|
listOfNotNull(showAutofillBadge)
|
||||||
}
|
}
|
||||||
|
@ -128,7 +130,7 @@ class FirstTimeActionManagerImpl @Inject constructor(
|
||||||
listOf(
|
listOf(
|
||||||
getShowImportLoginsFlowInternal(userId = activeUserId),
|
getShowImportLoginsFlowInternal(userId = activeUserId),
|
||||||
settingsDiskSource.getShowUnlockSettingBadgeFlow(userId = activeUserId),
|
settingsDiskSource.getShowUnlockSettingBadgeFlow(userId = activeUserId),
|
||||||
settingsDiskSource.getShowAutoFillSettingBadgeFlow(userId = activeUserId),
|
getShowAutofillSettingBadgeFlowInternal(userId = activeUserId),
|
||||||
getShowImportLoginsSettingBadgeFlowInternal(userId = activeUserId),
|
getShowImportLoginsSettingBadgeFlowInternal(userId = activeUserId),
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
@ -165,7 +167,7 @@ class FirstTimeActionManagerImpl @Inject constructor(
|
||||||
FirstTimeState(
|
FirstTimeState(
|
||||||
showImportLoginsCard = authDiskSource.getShowImportLogins(it),
|
showImportLoginsCard = authDiskSource.getShowImportLogins(it),
|
||||||
showSetupUnlockCard = settingsDiskSource.getShowUnlockSettingBadge(it),
|
showSetupUnlockCard = settingsDiskSource.getShowUnlockSettingBadge(it),
|
||||||
showSetupAutofillCard = settingsDiskSource.getShowAutoFillSettingBadge(it),
|
showSetupAutofillCard = getShowAutofillSettingBadgeInternal(it),
|
||||||
showImportLoginsCardInSettings = settingsDiskSource
|
showImportLoginsCardInSettings = settingsDiskSource
|
||||||
.getShowImportLoginsSettingBadge(it),
|
.getShowImportLoginsSettingBadge(it),
|
||||||
)
|
)
|
||||||
|
@ -236,4 +238,23 @@ class FirstTimeActionManagerImpl @Inject constructor(
|
||||||
showImportLogins ?: false && ciphers.isEmpty()
|
showImportLogins ?: false && ciphers.isEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal implementation to get a flow of the showAutofill value which takes
|
||||||
|
* into account if autofill is already enabled globally.
|
||||||
|
*/
|
||||||
|
private fun getShowAutofillSettingBadgeFlowInternal(userId: String): Flow<Boolean> {
|
||||||
|
return settingsDiskSource
|
||||||
|
.getShowAutoFillSettingBadgeFlow(userId)
|
||||||
|
.combine(
|
||||||
|
autofillEnabledManager.isAutofillEnabledStateFlow,
|
||||||
|
) { showAutofill, autofillEnabled ->
|
||||||
|
showAutofill ?: false && !autofillEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getShowAutofillSettingBadgeInternal(userId: String): Boolean {
|
||||||
|
return settingsDiskSource.getShowAutoFillSettingBadge(userId) ?: false &&
|
||||||
|
!autofillEnabledManager.isAutofillEnabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.core.content.getSystemService
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||||
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
import com.x8bit.bitwarden.data.auth.manager.AddTotpItemFromAuthenticatorManager
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.PushDiskSource
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||||
|
@ -293,19 +294,23 @@ object PlatformManagerModule {
|
||||||
vaultDiskSource: VaultDiskSource,
|
vaultDiskSource: VaultDiskSource,
|
||||||
dispatcherManager: DispatcherManager,
|
dispatcherManager: DispatcherManager,
|
||||||
featureFlagManager: FeatureFlagManager,
|
featureFlagManager: FeatureFlagManager,
|
||||||
|
autofillEnabledManager: AutofillEnabledManager,
|
||||||
): FirstTimeActionManager = FirstTimeActionManagerImpl(
|
): FirstTimeActionManager = FirstTimeActionManagerImpl(
|
||||||
authDiskSource = authDiskSource,
|
authDiskSource = authDiskSource,
|
||||||
settingsDiskSource = settingsDiskSource,
|
settingsDiskSource = settingsDiskSource,
|
||||||
vaultDiskSource = vaultDiskSource,
|
vaultDiskSource = vaultDiskSource,
|
||||||
dispatcherManager = dispatcherManager,
|
dispatcherManager = dispatcherManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
|
autofillEnabledManager = autofillEnabledManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDatabaseSchemeManager(
|
fun provideDatabaseSchemeManager(
|
||||||
settingsDiskSource: SettingsDiskSource,
|
settingsDiskSource: SettingsDiskSource,
|
||||||
|
dispatcherManager: DispatcherManager,
|
||||||
): DatabaseSchemeManager = DatabaseSchemeManagerImpl(
|
): DatabaseSchemeManager = DatabaseSchemeManagerImpl(
|
||||||
settingsDiskSource = settingsDiskSource,
|
settingsDiskSource = settingsDiskSource,
|
||||||
|
dispatcherManager = dispatcherManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||||
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of [AuthenticatorBridgeProcessor].
|
* Default implementation of [AuthenticatorBridgeProcessor].
|
||||||
|
@ -93,7 +94,13 @@ class AuthenticatorBridgeProcessorImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun syncAccounts() {
|
override fun syncAccounts() {
|
||||||
val symmetricEncryptionKey = symmetricEncryptionKeyData ?: return
|
val symmetricEncryptionKey = symmetricEncryptionKeyData ?: run {
|
||||||
|
Timber.e(
|
||||||
|
t = IllegalStateException(),
|
||||||
|
message = "Unable to sync accounts when symmetricEncryptionKeyData is null.",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
scope.launch {
|
scope.launch {
|
||||||
// Encrypt the shared account data with the symmetric key:
|
// Encrypt the shared account data with the symmetric key:
|
||||||
val encryptedSharedAccountData = authenticatorBridgeRepository
|
val encryptedSharedAccountData = authenticatorBridgeRepository
|
||||||
|
@ -110,14 +117,31 @@ class AuthenticatorBridgeProcessorImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startAddTotpLoginItemFlow(data: EncryptedAddTotpLoginItemData): Boolean {
|
override fun startAddTotpLoginItemFlow(data: EncryptedAddTotpLoginItemData): Boolean {
|
||||||
val symmetricEncryptionKey = symmetricEncryptionKeyData ?: return false
|
val symmetricEncryptionKey = symmetricEncryptionKeyData ?: run {
|
||||||
|
Timber.e(
|
||||||
|
t = IllegalStateException(),
|
||||||
|
message = "Unable to start add TOTP item flow when " +
|
||||||
|
"symmetricEncryptionKeyData is null.",
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
val intent = createAddTotpItemFromAuthenticatorIntent(context = applicationContext)
|
val intent = createAddTotpItemFromAuthenticatorIntent(context = applicationContext)
|
||||||
val totpData = data.decrypt(symmetricEncryptionKey)
|
val totpData = data.decrypt(symmetricEncryptionKey)
|
||||||
|
.onFailure {
|
||||||
|
Timber.e(t = it, message = "Unable to decrypt TOTP data.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
?.totpUri
|
?.totpUri
|
||||||
?.toUri()
|
?.toUri()
|
||||||
?.getTotpDataOrNull()
|
?.getTotpDataOrNull()
|
||||||
?: return false
|
?: run {
|
||||||
|
Timber.e(
|
||||||
|
t = IllegalStateException(),
|
||||||
|
message = "Unable to parse TOTP URI.",
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData = totpData
|
addTotpItemFromAuthenticatorManager.pendingAddTotpLoginItemData = totpData
|
||||||
applicationContext.startActivity(intent)
|
applicationContext.startActivity(intent)
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -14,9 +14,16 @@ inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> =
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given receiver of type [T] as a "success" [Result].
|
* Returns the given receiver of type [T] as a "success" [Result].
|
||||||
|
*
|
||||||
|
* Note that this will never double wrap the `Result` and we return the original value if [T] is
|
||||||
|
* already an instance of `Result`
|
||||||
*/
|
*/
|
||||||
fun <T> T.asSuccess(): Result<T> =
|
fun <T> T.asSuccess(): Result<T> = if (this is Result<*>) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
this as Result<T>
|
||||||
|
} else {
|
||||||
Result.success(this)
|
Result.success(this)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given [Throwable] as a "failure" [Result].
|
* Returns the given [Throwable] as a "failure" [Result].
|
||||||
|
|
|
@ -17,7 +17,6 @@ import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
|
@ -125,7 +124,6 @@ class VaultDiskSourceImpl(
|
||||||
override fun getDomains(userId: String): Flow<SyncResponseJson.Domains?> =
|
override fun getDomains(userId: String): Flow<SyncResponseJson.Domains?> =
|
||||||
domainsDao
|
domainsDao
|
||||||
.getDomains(userId)
|
.getDomains(userId)
|
||||||
.filterNotNull()
|
|
||||||
.map { entity ->
|
.map { entity ->
|
||||||
withContext(dispatcherManager.default) {
|
withContext(dispatcherManager.default) {
|
||||||
entity?.domainsJson?.let { json.decodeFromString<SyncResponseJson.Domains>(it) }
|
entity?.domainsJson?.let { json.decodeFromString<SyncResponseJson.Domains>(it) }
|
||||||
|
|
|
@ -26,7 +26,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity
|
||||||
FolderEntity::class,
|
FolderEntity::class,
|
||||||
SendEntity::class,
|
SendEntity::class,
|
||||||
],
|
],
|
||||||
version = 5,
|
version = 6,
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
)
|
)
|
||||||
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
||||||
|
|
|
@ -32,5 +32,5 @@ data class CollectionEntity(
|
||||||
val isReadOnly: Boolean,
|
val isReadOnly: Boolean,
|
||||||
|
|
||||||
@ColumnInfo(name = "manage")
|
@ColumnInfo(name = "manage")
|
||||||
val canManage: Boolean,
|
val canManage: Boolean?,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.Header
|
import retrofit2.http.Header
|
||||||
|
@ -21,5 +22,5 @@ interface AzureApi {
|
||||||
@Header("x-ms-date") date: String,
|
@Header("x-ms-date") date: String,
|
||||||
@Header("x-ms-version") version: String?,
|
@Header("x-ms-version") version: String?,
|
||||||
@Body body: RequestBody,
|
@Body body: RequestBody,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
|
||||||
|
@ -26,7 +27,7 @@ interface CiphersApi {
|
||||||
* Create a cipher.
|
* Create a cipher.
|
||||||
*/
|
*/
|
||||||
@POST("ciphers")
|
@POST("ciphers")
|
||||||
suspend fun createCipher(@Body body: CipherJsonRequest): Result<SyncResponseJson.Cipher>
|
suspend fun createCipher(@Body body: CipherJsonRequest): NetworkResult<SyncResponseJson.Cipher>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a cipher that belongs to an organization.
|
* Create a cipher that belongs to an organization.
|
||||||
|
@ -34,7 +35,7 @@ interface CiphersApi {
|
||||||
@POST("ciphers/create")
|
@POST("ciphers/create")
|
||||||
suspend fun createCipherInOrganization(
|
suspend fun createCipherInOrganization(
|
||||||
@Body body: CreateCipherInOrganizationJsonRequest,
|
@Body body: CreateCipherInOrganizationJsonRequest,
|
||||||
): Result<SyncResponseJson.Cipher>
|
): NetworkResult<SyncResponseJson.Cipher>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associates an attachment with a cipher.
|
* Associates an attachment with a cipher.
|
||||||
|
@ -43,7 +44,7 @@ interface CiphersApi {
|
||||||
suspend fun createAttachment(
|
suspend fun createAttachment(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Body body: AttachmentJsonRequest,
|
@Body body: AttachmentJsonRequest,
|
||||||
): Result<AttachmentJsonResponse>
|
): NetworkResult<AttachmentJsonResponse>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads the attachment associated with a cipher.
|
* Uploads the attachment associated with a cipher.
|
||||||
|
@ -53,7 +54,7 @@ interface CiphersApi {
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Path("attachmentId") attachmentId: String,
|
@Path("attachmentId") attachmentId: String,
|
||||||
@Body body: MultipartBody,
|
@Body body: MultipartBody,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a cipher.
|
* Updates a cipher.
|
||||||
|
@ -62,7 +63,7 @@ interface CiphersApi {
|
||||||
suspend fun updateCipher(
|
suspend fun updateCipher(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Body body: CipherJsonRequest,
|
@Body body: CipherJsonRequest,
|
||||||
): Result<SyncResponseJson.Cipher>
|
): NetworkResult<SyncResponseJson.Cipher>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shares a cipher.
|
* Shares a cipher.
|
||||||
|
@ -71,7 +72,7 @@ interface CiphersApi {
|
||||||
suspend fun shareCipher(
|
suspend fun shareCipher(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Body body: ShareCipherJsonRequest,
|
@Body body: ShareCipherJsonRequest,
|
||||||
): Result<SyncResponseJson.Cipher>
|
): NetworkResult<SyncResponseJson.Cipher>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shares an attachment.
|
* Shares an attachment.
|
||||||
|
@ -82,7 +83,7 @@ interface CiphersApi {
|
||||||
@Path("attachmentId") attachmentId: String,
|
@Path("attachmentId") attachmentId: String,
|
||||||
@Query("organizationId") organizationId: String?,
|
@Query("organizationId") organizationId: String?,
|
||||||
@Body body: MultipartBody,
|
@Body body: MultipartBody,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a cipher's collections.
|
* Updates a cipher's collections.
|
||||||
|
@ -91,7 +92,7 @@ interface CiphersApi {
|
||||||
suspend fun updateCipherCollections(
|
suspend fun updateCipherCollections(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Body body: UpdateCipherCollectionsJsonRequest,
|
@Body body: UpdateCipherCollectionsJsonRequest,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hard deletes a cipher.
|
* Hard deletes a cipher.
|
||||||
|
@ -99,7 +100,7 @@ interface CiphersApi {
|
||||||
@DELETE("ciphers/{cipherId}")
|
@DELETE("ciphers/{cipherId}")
|
||||||
suspend fun hardDeleteCipher(
|
suspend fun hardDeleteCipher(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Soft deletes a cipher.
|
* Soft deletes a cipher.
|
||||||
|
@ -107,7 +108,7 @@ interface CiphersApi {
|
||||||
@PUT("ciphers/{cipherId}/delete")
|
@PUT("ciphers/{cipherId}/delete")
|
||||||
suspend fun softDeleteCipher(
|
suspend fun softDeleteCipher(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an attachment from a cipher.
|
* Deletes an attachment from a cipher.
|
||||||
|
@ -116,7 +117,7 @@ interface CiphersApi {
|
||||||
suspend fun deleteCipherAttachment(
|
suspend fun deleteCipherAttachment(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Path("attachmentId") attachmentId: String,
|
@Path("attachmentId") attachmentId: String,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores a cipher.
|
* Restores a cipher.
|
||||||
|
@ -124,7 +125,7 @@ interface CiphersApi {
|
||||||
@PUT("ciphers/{cipherId}/restore")
|
@PUT("ciphers/{cipherId}/restore")
|
||||||
suspend fun restoreCipher(
|
suspend fun restoreCipher(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
): Result<SyncResponseJson.Cipher>
|
): NetworkResult<SyncResponseJson.Cipher>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a cipher.
|
* Gets a cipher.
|
||||||
|
@ -132,7 +133,7 @@ interface CiphersApi {
|
||||||
@GET("ciphers/{cipherId}")
|
@GET("ciphers/{cipherId}")
|
||||||
suspend fun getCipher(
|
suspend fun getCipher(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
): Result<SyncResponseJson.Cipher>
|
): NetworkResult<SyncResponseJson.Cipher>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a cipher attachment.
|
* Gets a cipher attachment.
|
||||||
|
@ -141,11 +142,11 @@ interface CiphersApi {
|
||||||
suspend fun getCipherAttachment(
|
suspend fun getCipherAttachment(
|
||||||
@Path("cipherId") cipherId: String,
|
@Path("cipherId") cipherId: String,
|
||||||
@Path("attachmentId") attachmentId: String,
|
@Path("attachmentId") attachmentId: String,
|
||||||
): Result<SyncResponseJson.Cipher.Attachment>
|
): NetworkResult<SyncResponseJson.Cipher.Attachment>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the active user has unassigned ciphers.
|
* Indicates if the active user has unassigned ciphers.
|
||||||
*/
|
*/
|
||||||
@GET("ciphers/has-unassigned-ciphers")
|
@GET("ciphers/has-unassigned-ciphers")
|
||||||
suspend fun hasUnassignedCiphers(): Result<Boolean>
|
suspend fun hasUnassignedCiphers(): NetworkResult<Boolean>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Streaming
|
import retrofit2.http.Streaming
|
||||||
|
@ -16,5 +17,5 @@ interface DownloadApi {
|
||||||
@Streaming
|
@Streaming
|
||||||
suspend fun getDataStream(
|
suspend fun getDataStream(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
): Result<ResponseBody>
|
): NetworkResult<ResponseBody>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
|
@ -18,7 +19,7 @@ interface FoldersApi {
|
||||||
* Create a folder.
|
* Create a folder.
|
||||||
*/
|
*/
|
||||||
@POST("folders")
|
@POST("folders")
|
||||||
suspend fun createFolder(@Body body: FolderJsonRequest): Result<SyncResponseJson.Folder>
|
suspend fun createFolder(@Body body: FolderJsonRequest): NetworkResult<SyncResponseJson.Folder>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a folder.
|
* Gets a folder.
|
||||||
|
@ -26,7 +27,7 @@ interface FoldersApi {
|
||||||
@GET("folders/{folderId}")
|
@GET("folders/{folderId}")
|
||||||
suspend fun getFolder(
|
suspend fun getFolder(
|
||||||
@Path("folderId") folderId: String,
|
@Path("folderId") folderId: String,
|
||||||
): Result<SyncResponseJson.Folder>
|
): NetworkResult<SyncResponseJson.Folder>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a folder.
|
* Updates a folder.
|
||||||
|
@ -35,11 +36,11 @@ interface FoldersApi {
|
||||||
suspend fun updateFolder(
|
suspend fun updateFolder(
|
||||||
@Path("folderId") folderId: String,
|
@Path("folderId") folderId: String,
|
||||||
@Body body: FolderJsonRequest,
|
@Body body: FolderJsonRequest,
|
||||||
): Result<SyncResponseJson.Folder>
|
): NetworkResult<SyncResponseJson.Folder>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a folder.
|
* Deletes a folder.
|
||||||
*/
|
*/
|
||||||
@DELETE("folders/{folderId}")
|
@DELETE("folders/{folderId}")
|
||||||
suspend fun deleteFolder(@Path("folderId") folderId: String): Result<Unit>
|
suspend fun deleteFolder(@Path("folderId") folderId: String): NetworkResult<Unit>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
|
@ -22,13 +23,15 @@ interface SendsApi {
|
||||||
* Create a text send.
|
* Create a text send.
|
||||||
*/
|
*/
|
||||||
@POST("sends")
|
@POST("sends")
|
||||||
suspend fun createTextSend(@Body body: SendJsonRequest): Result<SyncResponseJson.Send>
|
suspend fun createTextSend(@Body body: SendJsonRequest): NetworkResult<SyncResponseJson.Send>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a file send.
|
* Create a file send.
|
||||||
*/
|
*/
|
||||||
@POST("sends/file/v2")
|
@POST("sends/file/v2")
|
||||||
suspend fun createFileSend(@Body body: SendJsonRequest): Result<CreateFileSendResponseJson>
|
suspend fun createFileSend(
|
||||||
|
@Body body: SendJsonRequest,
|
||||||
|
): NetworkResult<CreateFileSendResponseJson>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a send.
|
* Updates a send.
|
||||||
|
@ -37,7 +40,7 @@ interface SendsApi {
|
||||||
suspend fun updateSend(
|
suspend fun updateSend(
|
||||||
@Path("sendId") sendId: String,
|
@Path("sendId") sendId: String,
|
||||||
@Body body: SendJsonRequest,
|
@Body body: SendJsonRequest,
|
||||||
): Result<SyncResponseJson.Send>
|
): NetworkResult<SyncResponseJson.Send>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads the file associated with a send.
|
* Uploads the file associated with a send.
|
||||||
|
@ -47,23 +50,25 @@ interface SendsApi {
|
||||||
@Path("sendId") sendId: String,
|
@Path("sendId") sendId: String,
|
||||||
@Path("fileId") fileId: String,
|
@Path("fileId") fileId: String,
|
||||||
@Body body: MultipartBody,
|
@Body body: MultipartBody,
|
||||||
): Result<Unit>
|
): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a send.
|
* Deletes a send.
|
||||||
*/
|
*/
|
||||||
@DELETE("sends/{sendId}")
|
@DELETE("sends/{sendId}")
|
||||||
suspend fun deleteSend(@Path("sendId") sendId: String): Result<Unit>
|
suspend fun deleteSend(@Path("sendId") sendId: String): NetworkResult<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a send.
|
* Deletes a send.
|
||||||
*/
|
*/
|
||||||
@PUT("sends/{sendId}/remove-password")
|
@PUT("sends/{sendId}/remove-password")
|
||||||
suspend fun removeSendPassword(@Path("sendId") sendId: String): Result<SyncResponseJson.Send>
|
suspend fun removeSendPassword(
|
||||||
|
@Path("sendId") sendId: String,
|
||||||
|
): NetworkResult<SyncResponseJson.Send>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a send.
|
* Gets a send.
|
||||||
*/
|
*/
|
||||||
@GET("sends/{sendId}")
|
@GET("sends/{sendId}")
|
||||||
suspend fun getSend(@Path("sendId") sendId: String): Result<SyncResponseJson.Send>
|
suspend fun getSend(@Path("sendId") sendId: String): NetworkResult<SyncResponseJson.Send>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
package com.x8bit.bitwarden.data.vault.datasource.network.api
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
@ -13,8 +14,8 @@ interface SyncApi {
|
||||||
* @return A [SyncResponseJson] containing the vault response model.
|
* @return A [SyncResponseJson] containing the vault response model.
|
||||||
*/
|
*/
|
||||||
@GET("sync")
|
@GET("sync")
|
||||||
suspend fun sync(): Result<SyncResponseJson>
|
suspend fun sync(): NetworkResult<SyncResponseJson>
|
||||||
|
|
||||||
@GET("/accounts/revision-date")
|
@GET("/accounts/revision-date")
|
||||||
suspend fun getAccountRevisionDateMillis(): Result<Long>
|
suspend fun getAccountRevisionDateMillis(): NetworkResult<Long>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
package com.x8bit.bitwarden.data.vault.datasource.network.model
|
||||||
|
|
||||||
import kotlinx.serialization.Contextual
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
|
@ -21,6 +23,7 @@ private const val DEFAULT_FIDO_2_KEY_CURVE = "P-256"
|
||||||
* @property domains A domains object associated with the vault data.
|
* @property domains A domains object associated with the vault data.
|
||||||
* @property sends A list of send objects associated with the vault data (nullable).
|
* @property sends A list of send objects associated with the vault data (nullable).
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SyncResponseJson(
|
data class SyncResponseJson(
|
||||||
@SerialName("folders")
|
@SerialName("folders")
|
||||||
|
@ -30,6 +33,7 @@ data class SyncResponseJson(
|
||||||
val collections: List<Collection>?,
|
val collections: List<Collection>?,
|
||||||
|
|
||||||
@SerialName("profile")
|
@SerialName("profile")
|
||||||
|
@JsonNames("Profile")
|
||||||
val profile: Profile,
|
val profile: Profile,
|
||||||
|
|
||||||
@SerialName("ciphers")
|
@SerialName("ciphers")
|
||||||
|
@ -39,6 +43,7 @@ data class SyncResponseJson(
|
||||||
val policies: List<Policy>?,
|
val policies: List<Policy>?,
|
||||||
|
|
||||||
@SerialName("domains")
|
@SerialName("domains")
|
||||||
|
@JsonNames("Domains")
|
||||||
val domains: Domains?,
|
val domains: Domains?,
|
||||||
|
|
||||||
@SerialName("sends")
|
@SerialName("sends")
|
||||||
|
@ -971,6 +976,6 @@ data class SyncResponseJson(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
||||||
@SerialName("manage")
|
@SerialName("manage")
|
||||||
val canManage: Boolean,
|
val canManage: Boolean?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.core.net.toUri
|
||||||
import com.bitwarden.vault.Attachment
|
import com.bitwarden.vault.Attachment
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
|
||||||
|
@ -34,20 +35,26 @@ class CiphersServiceImpl(
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) : CiphersService {
|
) : CiphersService {
|
||||||
override suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher> =
|
override suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher> =
|
||||||
ciphersApi.createCipher(body = body)
|
ciphersApi
|
||||||
|
.createCipher(body = body)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun createCipherInOrganization(
|
override suspend fun createCipherInOrganization(
|
||||||
body: CreateCipherInOrganizationJsonRequest,
|
body: CreateCipherInOrganizationJsonRequest,
|
||||||
): Result<SyncResponseJson.Cipher> = ciphersApi.createCipherInOrganization(body = body)
|
): Result<SyncResponseJson.Cipher> = ciphersApi
|
||||||
|
.createCipherInOrganization(body = body)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun createAttachment(
|
override suspend fun createAttachment(
|
||||||
cipherId: String,
|
cipherId: String,
|
||||||
body: AttachmentJsonRequest,
|
body: AttachmentJsonRequest,
|
||||||
): Result<AttachmentJsonResponse> =
|
): Result<AttachmentJsonResponse> =
|
||||||
ciphersApi.createAttachment(
|
ciphersApi
|
||||||
cipherId = cipherId,
|
.createAttachment(
|
||||||
body = body,
|
cipherId = cipherId,
|
||||||
)
|
body = body,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun uploadAttachment(
|
override suspend fun uploadAttachment(
|
||||||
attachmentJsonResponse: AttachmentJsonResponse,
|
attachmentJsonResponse: AttachmentJsonResponse,
|
||||||
|
@ -82,6 +89,7 @@ class CiphersServiceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.toResult()
|
||||||
.map { cipher }
|
.map { cipher }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,6 +102,7 @@ class CiphersServiceImpl(
|
||||||
cipherId = cipherId,
|
cipherId = cipherId,
|
||||||
body = body,
|
body = body,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
.map { UpdateCipherResponseJson.Success(cipher = it) }
|
.map { UpdateCipherResponseJson.Success(cipher = it) }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable
|
throwable
|
||||||
|
@ -115,77 +124,97 @@ class CiphersServiceImpl(
|
||||||
?: return IllegalStateException("Attachment must have ID").asFailure()
|
?: return IllegalStateException("Attachment must have ID").asFailure()
|
||||||
val attachmentKey = attachment.key
|
val attachmentKey = attachment.key
|
||||||
?: return IllegalStateException("Attachment must have Key").asFailure()
|
?: return IllegalStateException("Attachment must have Key").asFailure()
|
||||||
return ciphersApi.shareAttachment(
|
return ciphersApi
|
||||||
cipherId = cipherId,
|
.shareAttachment(
|
||||||
attachmentId = attachmentId,
|
cipherId = cipherId,
|
||||||
organizationId = organizationId,
|
attachmentId = attachmentId,
|
||||||
body = this
|
organizationId = organizationId,
|
||||||
.createMultipartBodyBuilder(
|
body = this
|
||||||
encryptedFile = encryptedFile,
|
.createMultipartBodyBuilder(
|
||||||
filename = attachment.fileName,
|
encryptedFile = encryptedFile,
|
||||||
)
|
filename = attachment.fileName,
|
||||||
.addPart(
|
)
|
||||||
part = MultipartBody.Part.createFormData(
|
.addPart(
|
||||||
name = "key",
|
part = MultipartBody.Part.createFormData(
|
||||||
value = attachmentKey,
|
name = "key",
|
||||||
),
|
value = attachmentKey,
|
||||||
)
|
),
|
||||||
.build(),
|
)
|
||||||
)
|
.build(),
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun shareCipher(
|
override suspend fun shareCipher(
|
||||||
cipherId: String,
|
cipherId: String,
|
||||||
body: ShareCipherJsonRequest,
|
body: ShareCipherJsonRequest,
|
||||||
): Result<SyncResponseJson.Cipher> =
|
): Result<SyncResponseJson.Cipher> =
|
||||||
ciphersApi.shareCipher(
|
ciphersApi
|
||||||
cipherId = cipherId,
|
.shareCipher(
|
||||||
body = body,
|
cipherId = cipherId,
|
||||||
)
|
body = body,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun updateCipherCollections(
|
override suspend fun updateCipherCollections(
|
||||||
cipherId: String,
|
cipherId: String,
|
||||||
body: UpdateCipherCollectionsJsonRequest,
|
body: UpdateCipherCollectionsJsonRequest,
|
||||||
): Result<Unit> =
|
): Result<Unit> =
|
||||||
ciphersApi.updateCipherCollections(
|
ciphersApi
|
||||||
cipherId = cipherId,
|
.updateCipherCollections(
|
||||||
body = body,
|
cipherId = cipherId,
|
||||||
)
|
body = body,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun hardDeleteCipher(cipherId: String): Result<Unit> =
|
override suspend fun hardDeleteCipher(cipherId: String): Result<Unit> =
|
||||||
ciphersApi.hardDeleteCipher(cipherId = cipherId)
|
ciphersApi
|
||||||
|
.hardDeleteCipher(cipherId = cipherId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun softDeleteCipher(cipherId: String): Result<Unit> =
|
override suspend fun softDeleteCipher(cipherId: String): Result<Unit> =
|
||||||
ciphersApi.softDeleteCipher(cipherId = cipherId)
|
ciphersApi
|
||||||
|
.softDeleteCipher(cipherId = cipherId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun deleteCipherAttachment(
|
override suspend fun deleteCipherAttachment(
|
||||||
cipherId: String,
|
cipherId: String,
|
||||||
attachmentId: String,
|
attachmentId: String,
|
||||||
): Result<Unit> =
|
): Result<Unit> =
|
||||||
ciphersApi.deleteCipherAttachment(
|
ciphersApi
|
||||||
cipherId = cipherId,
|
.deleteCipherAttachment(
|
||||||
attachmentId = attachmentId,
|
cipherId = cipherId,
|
||||||
)
|
attachmentId = attachmentId,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun restoreCipher(cipherId: String): Result<SyncResponseJson.Cipher> =
|
override suspend fun restoreCipher(cipherId: String): Result<SyncResponseJson.Cipher> =
|
||||||
ciphersApi.restoreCipher(cipherId = cipherId)
|
ciphersApi
|
||||||
|
.restoreCipher(cipherId = cipherId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getCipher(
|
override suspend fun getCipher(
|
||||||
cipherId: String,
|
cipherId: String,
|
||||||
): Result<SyncResponseJson.Cipher> =
|
): Result<SyncResponseJson.Cipher> =
|
||||||
ciphersApi.getCipher(cipherId = cipherId)
|
ciphersApi
|
||||||
|
.getCipher(cipherId = cipherId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getCipherAttachment(
|
override suspend fun getCipherAttachment(
|
||||||
cipherId: String,
|
cipherId: String,
|
||||||
attachmentId: String,
|
attachmentId: String,
|
||||||
): Result<SyncResponseJson.Cipher.Attachment> =
|
): Result<SyncResponseJson.Cipher.Attachment> =
|
||||||
ciphersApi.getCipherAttachment(
|
ciphersApi
|
||||||
cipherId = cipherId,
|
.getCipherAttachment(
|
||||||
attachmentId = attachmentId,
|
cipherId = cipherId,
|
||||||
)
|
attachmentId = attachmentId,
|
||||||
|
)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun hasUnassignedCiphers(): Result<Boolean> =
|
override suspend fun hasUnassignedCiphers(): Result<Boolean> =
|
||||||
ciphersApi.hasUnassignedCiphers()
|
ciphersApi
|
||||||
|
.hasUnassignedCiphers()
|
||||||
|
.toResult()
|
||||||
|
|
||||||
private fun createMultipartBodyBuilder(
|
private fun createMultipartBodyBuilder(
|
||||||
encryptedFile: File,
|
encryptedFile: File,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.DownloadApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.DownloadApi
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
|
||||||
|
@ -12,5 +13,7 @@ class DownloadServiceImpl(
|
||||||
override suspend fun getDataStream(
|
override suspend fun getDataStream(
|
||||||
url: String,
|
url: String,
|
||||||
): Result<ResponseBody> =
|
): Result<ResponseBody> =
|
||||||
downloadApi.getDataStream(url = url)
|
downloadApi
|
||||||
|
.getDataStream(url = url)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.FoldersApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.FoldersApi
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
|
@ -13,7 +14,9 @@ class FolderServiceImpl(
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
) : FolderService {
|
) : FolderService {
|
||||||
override suspend fun createFolder(body: FolderJsonRequest): Result<SyncResponseJson.Folder> =
|
override suspend fun createFolder(body: FolderJsonRequest): Result<SyncResponseJson.Folder> =
|
||||||
foldersApi.createFolder(body = body)
|
foldersApi
|
||||||
|
.createFolder(body = body)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun updateFolder(
|
override suspend fun updateFolder(
|
||||||
folderId: String,
|
folderId: String,
|
||||||
|
@ -24,6 +27,7 @@ class FolderServiceImpl(
|
||||||
folderId = folderId,
|
folderId = folderId,
|
||||||
body = body,
|
body = body,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
.map { UpdateFolderResponseJson.Success(folder = it) }
|
.map { UpdateFolderResponseJson.Success(folder = it) }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable
|
throwable
|
||||||
|
@ -36,10 +40,13 @@ class FolderServiceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteFolder(folderId: String): Result<Unit> =
|
override suspend fun deleteFolder(folderId: String): Result<Unit> =
|
||||||
foldersApi.deleteFolder(folderId = folderId)
|
foldersApi
|
||||||
|
.deleteFolder(folderId = folderId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getFolder(
|
override suspend fun getFolder(
|
||||||
folderId: String,
|
folderId: String,
|
||||||
): Result<SyncResponseJson.Folder> = foldersApi
|
): Result<SyncResponseJson.Folder> = foldersApi
|
||||||
.getFolder(folderId = folderId)
|
.getFolder(folderId = folderId)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
|
||||||
|
@ -34,7 +35,9 @@ class SendsServiceImpl(
|
||||||
override suspend fun createTextSend(
|
override suspend fun createTextSend(
|
||||||
body: SendJsonRequest,
|
body: SendJsonRequest,
|
||||||
): Result<CreateSendJsonResponse> =
|
): Result<CreateSendJsonResponse> =
|
||||||
sendsApi.createTextSend(body = body)
|
sendsApi
|
||||||
|
.createTextSend(body = body)
|
||||||
|
.toResult()
|
||||||
.map { CreateSendJsonResponse.Success(send = it) }
|
.map { CreateSendJsonResponse.Success(send = it) }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable.toBitwardenError()
|
throwable.toBitwardenError()
|
||||||
|
@ -48,7 +51,9 @@ class SendsServiceImpl(
|
||||||
override suspend fun createFileSend(
|
override suspend fun createFileSend(
|
||||||
body: SendJsonRequest,
|
body: SendJsonRequest,
|
||||||
): Result<CreateFileSendResponse> =
|
): Result<CreateFileSendResponse> =
|
||||||
sendsApi.createFileSend(body = body)
|
sendsApi
|
||||||
|
.createFileSend(body = body)
|
||||||
|
.toResult()
|
||||||
.map { CreateFileSendResponse.Success(it) }
|
.map { CreateFileSendResponse.Success(it) }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable.toBitwardenError()
|
throwable.toBitwardenError()
|
||||||
|
@ -68,6 +73,7 @@ class SendsServiceImpl(
|
||||||
sendId = sendId,
|
sendId = sendId,
|
||||||
body = body,
|
body = body,
|
||||||
)
|
)
|
||||||
|
.toResult()
|
||||||
.map { UpdateSendResponseJson.Success(send = it) }
|
.map { UpdateSendResponseJson.Success(send = it) }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable
|
throwable
|
||||||
|
@ -118,16 +124,20 @@ class SendsServiceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.toResult()
|
||||||
.onFailure { sendsApi.deleteSend(send.id) }
|
.onFailure { sendsApi.deleteSend(send.id) }
|
||||||
.map { send }
|
.map { send }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteSend(sendId: String): Result<Unit> =
|
override suspend fun deleteSend(sendId: String): Result<Unit> =
|
||||||
sendsApi.deleteSend(sendId = sendId)
|
sendsApi
|
||||||
|
.deleteSend(sendId = sendId)
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun removeSendPassword(sendId: String): Result<UpdateSendResponseJson> =
|
override suspend fun removeSendPassword(sendId: String): Result<UpdateSendResponseJson> =
|
||||||
sendsApi
|
sendsApi
|
||||||
.removeSendPassword(sendId = sendId)
|
.removeSendPassword(sendId = sendId)
|
||||||
|
.toResult()
|
||||||
.map { UpdateSendResponseJson.Success(send = it) }
|
.map { UpdateSendResponseJson.Success(send = it) }
|
||||||
.recoverCatching { throwable ->
|
.recoverCatching { throwable ->
|
||||||
throwable
|
throwable
|
||||||
|
@ -142,5 +152,7 @@ class SendsServiceImpl(
|
||||||
override suspend fun getSend(
|
override suspend fun getSend(
|
||||||
sendId: String,
|
sendId: String,
|
||||||
): Result<SyncResponseJson.Send> =
|
): Result<SyncResponseJson.Send> =
|
||||||
sendsApi.getSend(sendId = sendId)
|
sendsApi
|
||||||
|
.getSend(sendId = sendId)
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
package com.x8bit.bitwarden.data.vault.datasource.network.service
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi
|
import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi
|
||||||
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
|
||||||
|
|
||||||
class SyncServiceImpl(
|
class SyncServiceImpl(
|
||||||
private val syncApi: SyncApi,
|
private val syncApi: SyncApi,
|
||||||
) : SyncService {
|
) : SyncService {
|
||||||
override suspend fun sync(): Result<SyncResponseJson> = syncApi.sync()
|
override suspend fun sync(): Result<SyncResponseJson> = syncApi
|
||||||
|
.sync()
|
||||||
|
.toResult()
|
||||||
|
|
||||||
override suspend fun getAccountRevisionDateMillis(): Result<Long> =
|
override suspend fun getAccountRevisionDateMillis(): Result<Long> =
|
||||||
syncApi.getAccountRevisionDateMillis()
|
syncApi
|
||||||
|
.getAccountRevisionDateMillis()
|
||||||
|
.toResult()
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,15 @@ interface VaultSdkSource {
|
||||||
encryptedPin: String,
|
encryptedPin: String,
|
||||||
): Result<String>
|
): Result<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the user pin using the [pinProtectedUserKey].
|
||||||
|
*/
|
||||||
|
suspend fun validatePin(
|
||||||
|
userId: String,
|
||||||
|
pin: String,
|
||||||
|
pinProtectedUserKey: String,
|
||||||
|
): Result<Boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the key for an auth request that is required to approve or decline it.
|
* Gets the key for an auth request that is required to approve or decline it.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -109,6 +109,17 @@ class VaultSdkSourceImpl(
|
||||||
.derivePinUserKey(encryptedPin = encryptedPin)
|
.derivePinUserKey(encryptedPin = encryptedPin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun validatePin(
|
||||||
|
userId: String,
|
||||||
|
pin: String,
|
||||||
|
pinProtectedUserKey: String,
|
||||||
|
): Result<Boolean> =
|
||||||
|
runCatchingWithLogs {
|
||||||
|
getClient(userId = userId)
|
||||||
|
.auth()
|
||||||
|
.validatePin(pin = pin, pinProtectedUserKey = pinProtectedUserKey)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getAuthRequestKey(
|
override suspend fun getAuthRequestKey(
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
userId: String,
|
userId: String,
|
||||||
|
|
|
@ -99,6 +99,7 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
@ -323,6 +324,12 @@ class VaultRepositoryImpl(
|
||||||
.syncFolderUpsertFlow
|
.syncFolderUpsertFlow
|
||||||
.onEach(::syncFolderIfNecessary)
|
.onEach(::syncFolderIfNecessary)
|
||||||
.launchIn(ioScope)
|
.launchIn(ioScope)
|
||||||
|
|
||||||
|
databaseSchemeManager
|
||||||
|
.lastDatabaseSchemeChangeInstantFlow
|
||||||
|
.filterNotNull()
|
||||||
|
.onEach { sync() }
|
||||||
|
.launchIn(ioScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearUnlockedData() {
|
private fun clearUnlockedData() {
|
||||||
|
|
|
@ -17,7 +17,7 @@ fun SyncResponseJson.Collection.toEncryptedSdkCollection(): Collection =
|
||||||
externalId = this.externalId,
|
externalId = this.externalId,
|
||||||
hidePasswords = this.shouldHidePasswords,
|
hidePasswords = this.shouldHidePasswords,
|
||||||
readOnly = this.isReadOnly,
|
readOnly = this.isReadOnly,
|
||||||
manage = this.canManage,
|
manage = this.canManage ?: !this.isReadOnly,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
@ -134,14 +133,13 @@ fun SetupAutoFillScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
SetupAutoFillContent(
|
SetupAutoFillContent(
|
||||||
state = state,
|
state = state,
|
||||||
onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) },
|
onAutofillServiceChanged = { handler.onAutofillServiceChanged(it) },
|
||||||
onContinueClick = handler.onContinueClick,
|
onContinueClick = handler.onContinueClick,
|
||||||
onTurnOnLaterClick = handler.onTurnOnLaterClick,
|
onTurnOnLaterClick = handler.onTurnOnLaterClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
@ -62,11 +61,9 @@ fun SetupCompleteScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
) { innerPadding ->
|
) {
|
||||||
SetupCompleteContent(
|
SetupCompleteContent(
|
||||||
modifier = Modifier
|
modifier = Modifier.verticalScroll(rememberScrollState()),
|
||||||
.padding(innerPadding)
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
onContinue = setupCompleteAction,
|
onContinue = setupCompleteAction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,15 +128,13 @@ fun SetupUnlockScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
SetupUnlockScreenContent(
|
SetupUnlockScreenContent(
|
||||||
state = state,
|
state = state,
|
||||||
showBiometricsPrompt = showBiometricsPrompt,
|
showBiometricsPrompt = showBiometricsPrompt,
|
||||||
handler = handler,
|
handler = handler,
|
||||||
biometricsManager = biometricsManager,
|
biometricsManager = biometricsManager,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.padding(paddingValues = innerPadding)
|
|
||||||
.fillMaxSize(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,11 @@ class SetupUnlockViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleCloseClick() {
|
private fun handleCloseClick() {
|
||||||
|
// If the user has enabled biometric or PIN lock, but then closes the screen we
|
||||||
|
// want to dismiss the action card.
|
||||||
|
if (state.isContinueButtonEnabled) {
|
||||||
|
firstTimeActionManager.storeShowUnlockSettingBadge(showBadge = false)
|
||||||
|
}
|
||||||
sendEvent(SetupUnlockEvent.NavigateBack)
|
sendEvent(SetupUnlockEvent.NavigateBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,10 +91,9 @@ fun CheckEmailScreen(
|
||||||
onNavigationIconClick = handler.onBackClick,
|
onNavigationIconClick = handler.onBackClick,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
|
@ -298,7 +297,7 @@ private fun CheckEmailLegacyContent(
|
||||||
),
|
),
|
||||||
highlights = listOf(
|
highlights = listOf(
|
||||||
ClickableTextHighlight(
|
ClickableTextHighlight(
|
||||||
textToHighlight = stringResource(id = R.string.log_in),
|
textToHighlight = stringResource(id = R.string.log_in_verb),
|
||||||
onTextClick = onLoginClick,
|
onTextClick = onLoginClick,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
@ -158,10 +157,9 @@ fun CompleteRegistrationScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
|
|
|
@ -42,14 +42,20 @@ fun PasswordStrengthIndicator(
|
||||||
currentCharacterCount: Int,
|
currentCharacterCount: Int,
|
||||||
minimumCharacterCount: Int? = null,
|
minimumCharacterCount: Int? = null,
|
||||||
) {
|
) {
|
||||||
|
val minimumRequirementMet = (minimumCharacterCount == null) ||
|
||||||
|
(currentCharacterCount >= minimumCharacterCount)
|
||||||
val widthPercent by animateFloatAsState(
|
val widthPercent by animateFloatAsState(
|
||||||
targetValue = when (state) {
|
targetValue = if (minimumRequirementMet) {
|
||||||
PasswordStrengthState.NONE -> 0f
|
when (state) {
|
||||||
PasswordStrengthState.WEAK_1 -> .25f
|
PasswordStrengthState.NONE -> 0f
|
||||||
PasswordStrengthState.WEAK_2 -> .5f
|
PasswordStrengthState.WEAK_1 -> .25f
|
||||||
PasswordStrengthState.WEAK_3 -> .66f
|
PasswordStrengthState.WEAK_2 -> .5f
|
||||||
PasswordStrengthState.GOOD -> .82f
|
PasswordStrengthState.WEAK_3 -> .66f
|
||||||
PasswordStrengthState.STRONG -> 1f
|
PasswordStrengthState.GOOD -> .82f
|
||||||
|
PasswordStrengthState.STRONG -> 1f
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
},
|
},
|
||||||
label = "Width Percent State",
|
label = "Width Percent State",
|
||||||
)
|
)
|
||||||
|
@ -107,11 +113,13 @@ fun PasswordStrengthIndicator(
|
||||||
minimumCharacterCount = minCount,
|
minimumCharacterCount = minCount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Text(
|
if (minimumRequirementMet) {
|
||||||
text = label(),
|
Text(
|
||||||
style = BitwardenTheme.typography.labelSmall,
|
text = label(),
|
||||||
color = indicatorColor,
|
style = BitwardenTheme.typography.labelSmall,
|
||||||
)
|
color = indicatorColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,14 +130,6 @@ private fun MinimumCharacterCount(
|
||||||
minimumRequirementMet: Boolean,
|
minimumRequirementMet: Boolean,
|
||||||
minimumCharacterCount: Int,
|
minimumCharacterCount: Int,
|
||||||
) {
|
) {
|
||||||
val characterCountColor by animateColorAsState(
|
|
||||||
targetValue = if (minimumRequirementMet) {
|
|
||||||
BitwardenTheme.colorScheme.status.strong
|
|
||||||
} else {
|
|
||||||
BitwardenTheme.colorScheme.text.secondary
|
|
||||||
},
|
|
||||||
label = "minmumCharacterCountColor",
|
|
||||||
)
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
@ -145,14 +145,14 @@ private fun MinimumCharacterCount(
|
||||||
Icon(
|
Icon(
|
||||||
painter = rememberVectorPainter(id = it),
|
painter = rememberVectorPainter(id = it),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = characterCountColor,
|
tint = BitwardenTheme.colorScheme.text.secondary,
|
||||||
modifier = Modifier.size(12.dp),
|
modifier = Modifier.size(12.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(2.dp))
|
Spacer(modifier = Modifier.width(2.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.minimum_characters, minimumCharacterCount),
|
text = stringResource(R.string.minimum_characters, minimumCharacterCount),
|
||||||
color = characterCountColor,
|
color = BitwardenTheme.colorScheme.text.secondary,
|
||||||
style = BitwardenTheme.typography.labelSmall,
|
style = BitwardenTheme.typography.labelSmall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package com.x8bit.bitwarden.ui.auth.feature.createaccount
|
package com.x8bit.bitwarden.ui.auth.feature.createaccount
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
@ -17,31 +14,23 @@ import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Switch
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.material3.ripple
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.semantics
|
|
||||||
import androidx.compose.ui.semantics.testTag
|
|
||||||
import androidx.compose.ui.semantics.toggleableState
|
|
||||||
import androidx.compose.ui.state.ToggleableState
|
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
@ -76,7 +65,6 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||||
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
|
||||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
|
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
|
||||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||||
import com.x8bit.bitwarden.ui.platform.components.toggle.color.bitwardenSwitchColors
|
|
||||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||||
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
|
@ -187,10 +175,9 @@ fun CreateAccountScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
|
@ -284,6 +271,9 @@ fun CreateAccountScreen(
|
||||||
onPrivacyPolicyClick = remember(viewModel) {
|
onPrivacyPolicyClick = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(PrivacyPolicyClick) }
|
{ viewModel.trySendAction(PrivacyPolicyClick) }
|
||||||
},
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||||
}
|
}
|
||||||
|
@ -291,52 +281,24 @@ fun CreateAccountScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Suppress("LongMethod")
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TermsAndPrivacySwitch(
|
private fun TermsAndPrivacySwitch(
|
||||||
isChecked: Boolean,
|
isChecked: Boolean,
|
||||||
onCheckedChange: (Boolean) -> Unit,
|
onCheckedChange: (Boolean) -> Unit,
|
||||||
onTermsClick: () -> Unit,
|
onTermsClick: () -> Unit,
|
||||||
onPrivacyPolicyClick: () -> Unit,
|
onPrivacyPolicyClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Row(
|
BitwardenSwitch(
|
||||||
horizontalArrangement = Arrangement.Start,
|
modifier = modifier,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
label = stringResource(id = R.string.accept_policies),
|
||||||
modifier = Modifier
|
isChecked = isChecked,
|
||||||
.semantics(mergeDescendants = true) {
|
contentDescription = "AcceptPoliciesToggle",
|
||||||
testTag = "AcceptPoliciesToggle"
|
onCheckedChange = onCheckedChange,
|
||||||
toggleableState = ToggleableState(isChecked)
|
subContent = {
|
||||||
}
|
|
||||||
.clickable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = ripple(
|
|
||||||
color = BitwardenTheme.colorScheme.background.pressed,
|
|
||||||
),
|
|
||||||
onClick = { onCheckedChange.invoke(!isChecked) },
|
|
||||||
)
|
|
||||||
.padding(start = 16.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
Switch(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(32.dp)
|
|
||||||
.width(52.dp),
|
|
||||||
checked = isChecked,
|
|
||||||
onCheckedChange = null,
|
|
||||||
colors = bitwardenSwitchColors(),
|
|
||||||
)
|
|
||||||
Column(Modifier.padding(start = 16.dp, top = 4.dp, bottom = 4.dp)) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.accept_policies),
|
|
||||||
style = BitwardenTheme.typography.bodyLarge,
|
|
||||||
color = BitwardenTheme.colorScheme.text.primary,
|
|
||||||
)
|
|
||||||
FlowRow(
|
FlowRow(
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.padding(end = 16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
) {
|
) {
|
||||||
BitwardenClickableText(
|
BitwardenClickableText(
|
||||||
label = stringResource(id = R.string.terms_of_service),
|
label = stringResource(id = R.string.terms_of_service),
|
||||||
|
@ -358,6 +320,6 @@ private fun TermsAndPrivacySwitch(
|
||||||
innerPadding = PaddingValues(vertical = 4.dp, horizontal = 0.dp),
|
innerPadding = PaddingValues(vertical = 4.dp, horizontal = 0.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ fun EnterpriseSignOnScreen(
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
BitwardenTextButton(
|
BitwardenTextButton(
|
||||||
label = stringResource(id = R.string.log_in),
|
label = stringResource(id = R.string.log_in_verb),
|
||||||
onClick = remember(viewModel) {
|
onClick = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(EnterpriseSignOnAction.LogInClick) }
|
{ viewModel.trySendAction(EnterpriseSignOnAction.LogInClick) }
|
||||||
},
|
},
|
||||||
|
@ -126,15 +126,13 @@ fun EnterpriseSignOnScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
EnterpriseSignOnScreenContent(
|
EnterpriseSignOnScreenContent(
|
||||||
state = state,
|
state = state,
|
||||||
onOrgIdentifierInputChange = remember(viewModel) {
|
onOrgIdentifierInputChange = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(EnterpriseSignOnAction.OrgIdentifierInputChange(it)) }
|
{ viewModel.trySendAction(EnterpriseSignOnAction.OrgIdentifierInputChange(it)) }
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.padding(innerPadding)
|
|
||||||
.fillMaxSize(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,10 +98,9 @@ fun EnvironmentScreen(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.imePadding()
|
.imePadding()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
@ -72,7 +71,7 @@ fun ExpiredRegistrationLinkScreen(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) {
|
||||||
ExpiredRegistrationLinkContent(
|
ExpiredRegistrationLinkContent(
|
||||||
onNavigateToLogin = remember(viewModel) {
|
onNavigateToLogin = remember(viewModel) {
|
||||||
{
|
{
|
||||||
|
@ -87,7 +86,6 @@ fun ExpiredRegistrationLinkScreen(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState()),
|
.verticalScroll(rememberScrollState()),
|
||||||
)
|
)
|
||||||
|
|
|
@ -148,7 +148,29 @@ fun LandingScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
overlay = {
|
||||||
|
BitwardenAccountSwitcher(
|
||||||
|
isVisible = isAccountMenuVisible,
|
||||||
|
accountSummaries = state.accountSummaries.toImmutableList(),
|
||||||
|
onSwitchAccountClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(LandingAction.SwitchAccountClick(it)) }
|
||||||
|
},
|
||||||
|
onLockAccountClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(LandingAction.LockAccountClick(it)) }
|
||||||
|
},
|
||||||
|
onLogoutAccountClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(LandingAction.LogoutAccountClick(it)) }
|
||||||
|
},
|
||||||
|
onAddAccountClick = {
|
||||||
|
// Not available
|
||||||
|
},
|
||||||
|
onDismissRequest = { isAccountMenuVisible = false },
|
||||||
|
isAddAccountAvailable = false,
|
||||||
|
topAppBarScrollBehavior = scrollBehavior,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
LandingScreenContent(
|
LandingScreenContent(
|
||||||
state = state,
|
state = state,
|
||||||
isAppBarVisible = isAppBarVisible,
|
isAppBarVisible = isAppBarVisible,
|
||||||
|
@ -167,32 +189,7 @@ fun LandingScreen(
|
||||||
onCreateAccountClick = remember(viewModel) {
|
onCreateAccountClick = remember(viewModel) {
|
||||||
{ viewModel.trySendAction(LandingAction.CreateAccountClick) }
|
{ viewModel.trySendAction(LandingAction.CreateAccountClick) }
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.padding(innerPadding)
|
|
||||||
.fillMaxSize(),
|
|
||||||
)
|
|
||||||
|
|
||||||
BitwardenAccountSwitcher(
|
|
||||||
isVisible = isAccountMenuVisible,
|
|
||||||
accountSummaries = state.accountSummaries.toImmutableList(),
|
|
||||||
onSwitchAccountClick = remember(viewModel) {
|
|
||||||
{ viewModel.trySendAction(LandingAction.SwitchAccountClick(it)) }
|
|
||||||
},
|
|
||||||
onLockAccountClick = remember(viewModel) {
|
|
||||||
{ viewModel.trySendAction(LandingAction.LockAccountClick(it)) }
|
|
||||||
},
|
|
||||||
onLogoutAccountClick = remember(viewModel) {
|
|
||||||
{ viewModel.trySendAction(LandingAction.LogoutAccountClick(it)) }
|
|
||||||
},
|
|
||||||
onAddAccountClick = {
|
|
||||||
// Not available
|
|
||||||
},
|
|
||||||
onDismissRequest = { isAccountMenuVisible = false },
|
|
||||||
isAddAccountAvailable = false,
|
|
||||||
topAppBarScrollBehavior = scrollBehavior,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(innerPadding)
|
|
||||||
.fillMaxSize(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue