Skip to content

[PM-30703] Introduce CXF payload parser and update to alpha05#6347

Merged
SaintPatrck merged 5 commits intomainfrom
cred-exchange/dep-updates
Feb 17, 2026
Merged

[PM-30703] Introduce CXF payload parser and update to alpha05#6347
SaintPatrck merged 5 commits intomainfrom
cred-exchange/dep-updates

Conversation

@SaintPatrck
Copy link
Contributor

@SaintPatrck SaintPatrck commented Jan 12, 2026

🎟️ Tracking

PM-30703

📔 Objective

Update the Credential Exchange (CXF/CXP) implementation to align with the androidx.credentials.providerevents:1.0.0-alpha04 library. The previous implementation was based on alpha03 and is now obsolete.

The primary behavioral change is the removal of the two-layered JSON structure (CXP wrapping CXF). The alpha04 specification simplifies this to a single, direct JSON payload for credential exchange.

Specific changes include:

  • Dependency Update:

    • Upgraded androidx.credentials.providerevents from 1.0.0-alpha03 to 1.0.0-alpha05.
  • Payload Parsing:

    • Introduced a CredentialExchangePayloadParser to handle the new direct JSON payload structure. This parser validates the CXF version, checks for the presence of accounts, and handles various error conditions.
    • The CredentialExchangeImportManager is refactored to use this new parser, removing the now-unnecessary logic for decoding the two-layered, base64-encoded payload.
  • Import/Export Flow:

    • CredentialExchangeImporterImpl: Updated to use the modern ImportCredentialsRequest builder, which no longer requires manual JSON construction.
    • CredentialExchangeCompletionManagerImpl: Modified to return the new, simplified direct JSON response, removing the CXP wrapper and base64 encoding.
    • CredentialExchangeRegistryImpl: Aligned with alpha04 by including a default export_matcher.bin (a WASM binary from Google) in the registration request.
  • DI and Module Structure:

    • Added a new CxfModule to provide the CredentialExchangePayloadParser.
    • Updated build.gradle.kts for the cxf module to include Hilt dependencies for DI.
  • Testing:

    • Added extensive unit tests for the new CredentialExchangePayloadParser to cover valid payloads, version checks, and various error states.
    • Updated existing tests for CredentialExchangeImporterTest and CredentialExchangeImportManagerTest to reflect the API and structural changes from the library update.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@github-actions github-actions bot added app:password-manager Bitwarden Password Manager app context app:authenticator Bitwarden Authenticator app context t:deps Change Type - Dependencies labels Jan 12, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 12, 2026

Logo
Checkmarx One – Scan Summary & Details31ec83c4-bcb2-4d20-92cd-4c41c443db97

Great job! No new security vulnerabilities introduced in this pull request

@SaintPatrck SaintPatrck force-pushed the cred-exchange/dep-updates branch from 037610f to 80720fb Compare January 12, 2026 18:26
@SaintPatrck SaintPatrck marked this pull request as ready for review January 12, 2026 18:28
@SaintPatrck SaintPatrck requested review from a team and david-livefront as code owners January 12, 2026 18:28
@SaintPatrck SaintPatrck added the ai-review Request a Claude code review label Jan 12, 2026
@claude
Copy link
Contributor

claude bot commented Jan 12, 2026

Claude finished @SaintPatrck's task in 5m 42s —— View job


Code Review: PR #6347 - PM-30703 Introduce CXF payload parser and update to alpha05

  • Gather context and read changed files
  • Review code for bugs, security issues, and best practices
  • Post review findings

Summary

This PR cleanly updates the Credential Exchange implementation from alpha03 to alpha05, removing the two-layered CXP/CXF structure in favor of a direct JSON payload. The new CredentialExchangePayloadParser is well-structured, the import manager now processes all accounts via flatMap, and test coverage is comprehensive. The overall architecture follows established patterns well.

Findings

🎨 Suggestion: CxfModule missing @Singleton scopecxf/src/main/kotlin/com/bitwarden/cxf/di/CxfModule.kt:18-22

The provideCredentialExchangePayloadParser function lacks @Singleton annotation. While CredentialExchangePayloadParserImpl is stateless, the CredentialExchangeImportManager that depends on it is @Singleton-scoped (see VaultManagerModule.kt:249). Without scoping, Hilt creates a new parser instance each time it's injected. This is functionally harmless since it's stateless, but inconsistent with how other dependencies in this graph are scoped.


🤔 Discussion: JSON string interpolation in CredentialExchangeCompletionManagerImplcxf/src/main/kotlin/com/bitwarden/cxf/manager/CredentialExchangeCompletionManagerImpl.kt:32-44

The export response JSON is constructed via string interpolation:

"exporterRpId": "${activity.packageName}",
"exporterDisplayName": "${activity.applicationInfo.name}",
...
"accounts": [${exportResult.payload}]

While packageName is safe and payload is SDK-generated, applicationInfo.name is technically user-facing metadata. If it ever contained characters like " or \, it would produce malformed JSON. This is a pre-existing pattern (not introduced in this PR), so it's fine to leave as-is — just noting it as a potential follow-up if you ever move to a serialization-based approach for constructing these responses.


👍 Well done: Multi-account processingapp/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CredentialExchangeImportManagerImpl.kt:58-62

The flatMap approach for processing multiple accounts is clean and idiomatic:

val allCiphers = accountsJsonList.flatMap { accountJson ->
    vaultSdkSource
        .importCxf(userId = userId, payload = accountJson)
        .getOrElse { return ImportCxfPayloadResult.Error(error = it) }
}

The early return on first failure is a reasonable strategy for this use case.


👍 Test coverage — Parser tests cover valid payloads, multi-account, empty accounts, unsupported versions, invalid JSON, and serialization errors. Import manager tests cover multi-account aggregation, partial failure (second account), policy filtering, and all result paths. The CredentialExchangeImporterTest now covers all three catch blocks.


👍 .use{} on InputStreamcxf/src/main/kotlin/com/bitwarden/cxf/registry/CredentialExchangeRegistryImpl.kt:40 — Already addressed from previous review feedback.


Overall this is a solid PR. The simplification from the two-layered structure to direct JSON is a welcome change, and the parser abstraction makes the import manager much cleaner.

} else {
cipherList
}
.map { syncVault(it) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: Inconsistent error handling loses context

The uploadCiphers function converts specific ImportCiphersResponseJson.Invalid responses into a generic ImportCredentialsUnknownErrorException, losing valuable error context.

Problems:

  1. Lost validation errors: Invalid contains a validationErrors map with specific field-level errors, but this is completely discarded
  2. Debugging difficulty: Support and developers lose insight into why the import failed on the server side
  3. User experience: Generic errors provide no actionable feedback

Recommended fix:

is ImportCiphersResponseJson.Invalid -> {
    // Log validation errors for debugging
    Timber.w("Import ciphers validation failed: ${response.validationErrors}")
    Result.failure(
        ImportCredentialsUnknownErrorException(
            "Server validation failed: ${response.validationErrors.keys.joinToString()}"
        )
    )
}

Alternatively, consider creating a custom exception type that preserves the validation errors for upstream handling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log added.

@github-actions github-actions bot removed the ai-review Request a Claude code review label Jan 12, 2026
@SaintPatrck SaintPatrck force-pushed the cred-exchange/dep-updates branch 4 times, most recently from b5cb0dd to 9de5ff0 Compare January 12, 2026 21:29
@codecov
Copy link

codecov bot commented Jan 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.34%. Comparing base (34a7c44) to head (4cb594e).
⚠️ Report is 6 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6347   +/-   ##
=======================================
  Coverage   86.34%   86.34%           
=======================================
  Files         794      792    -2     
  Lines       56782    56712   -70     
  Branches     8213     8214    +1     
=======================================
- Hits        49028    48968   -60     
+ Misses       4899     4889   -10     
  Partials     2855     2855           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

SaintPatrck and others added 3 commits February 17, 2026 09:38
…d parser

This commit updates the Credential Exchange (CXF) implementation to align with the `androidx.credentials.providerevents:1.0.0-alpha04` library. The primary objective is to adapt to the breaking changes introduced in this version, which simplifies the API and removes the custom JSON request/response structure in favor of direct parameters.

A key part of this refactor is the introduction of a dedicated `CredentialExchangePayloadParser`. This parser is responsible for validating and processing the incoming JSON payload from the exporting credential manager.

Behavioral changes:
- The structure of the `ImportCredentialsRequest` sent to the credential provider has changed. It no longer uses a custom JSON string but instead passes `credentialTypes` and `knownExtensions` directly.
- The response from the credential provider is no longer a nested JSON structure with a Base64 encoded payload. It is now a direct JSON object representing the exported data.

Specific changes:
- **Dependencies**:
    - Upgraded `androidx.credentials.providerevents` to `1.0.0-alpha04`.
    - Added Hilt dependencies to the `cxf` module for dependency injection.

- **CXF Payload Parsing**:
    - Created `CredentialExchangePayloadParser` interface and its `CredentialExchangePayloadParserImpl` implementation to handle the parsing of incoming CXF JSON data.
    - Introduced a `CredentialExchangePayload` sealed class to represent the different outcomes of parsing: `Importable`, `NoItems`, and `Error`.
    - Added a `CxfModule` to provide the parser implementation via Hilt.
    - Added comprehensive unit tests for `CredentialExchangePayloadParserImpl` to cover valid payloads, version checks, invalid JSON, and error conditions.

- **Import Logic**:
    - Refactored `CredentialExchangeImporterImpl` to use the new `ImportCredentialsRequest` constructor, passing `credentialTypes` and `knownExtensions` directly instead of building a JSON string.
    - Updated `CredentialExchangeImportManagerImpl` to use the new `CredentialExchangePayloadParser`. The manager now delegates payload validation and parsing, simplifying its own logic to focus on the import, upload, and sync process.
    - Removed the now-obsolete manual parsing of the two-layered CXP/CXF JSON structure and Base64 decoding.

- **Export Logic**:
    - In `CredentialExchangeRegistryImpl`, added the `exportMatcher` WASM binary required by the `alpha04` API when registering an export flow.
    - Simplified `CredentialExchangeCompletionManagerImpl` to directly return the export data as a JSON string, removing the previous logic that wrapped it in a `CXP` protocol message with Base64 encoding.

- **Testing**:
    - Updated unit tests across `app` and `cxf` modules (`CredentialExchangeImporterTest`, `MainViewModelTest`, `CredentialExchangeImportManagerTest`, etc.) to reflect the API changes in `ImportCredentialsRequest` and the new parsing flow.
    - Removed outdated test logic related to the old JSON structure and Base64 decoding.
Log validation errors from ImportCiphersResponseJson.Invalid before
returning the failure, preserving debugging context.

Co-Authored-By: Claude <noreply@anthropic.com>
Update the CXF payload parser and import manager to process all
accounts in a credential exchange payload instead of only the first.
Each account is serialized individually and passed to the SDK, with
ciphers aggregated into a single batch upload. Import fails fast if
any account's SDK call fails. Also bumps credentials provider events
dependency to alpha05.

Co-Authored-By: Claude <noreply@anthropic.com>
@SaintPatrck SaintPatrck force-pushed the cred-exchange/dep-updates branch from d71150d to 112204e Compare February 17, 2026 14:42
@SaintPatrck SaintPatrck added the ai-review Request a Claude code review label Feb 17, 2026
Close InputStream from openRawResource() using .use {} to prevent
resource leaks. Add test verifying ImportCredentialsException catch
path preserves the original exception.

Co-Authored-By: Claude <noreply@anthropic.com>
@SaintPatrck SaintPatrck changed the title [PM-30703] Introduce CXF payload parser and update to alpha04 [PM-30703] Introduce CXF payload parser and update to alpha05 Feb 17, 2026
CredentialExchangePayload.Importable(
accountsJsonList = accountsJsonList,
)
} catch (_: SerializationException) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we log this for the flight recorder?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a log statement. I'm not comfortable passing the exception to Timber since it could contain sensitive details.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh, that makes sense

vaultSyncManager = vaultSyncManager,
policyManager = policyManager,
json = json,
credentialExchangePayloadParser = credentialExchangePayloadParser,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

userId: String,
accountsJsonList: List<String>,
): ImportCxfPayloadResult {
val allCiphers = mutableListOf<Cipher>()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the mutable list can we do something like this?

val allCiphers = accountsJsonList.flatMap { accountJson ->
    vaultSdkSource
        .importCxf(userId = userId, payload = accountJson)
        .getOrElse { return ImportCxfPayloadResult.Error(error = it) }
}

This commit refactors the credential exchange import process for improved conciseness and adds error logging for better diagnostics.

There are no behavioral changes for the end-user.

**Changes:**

*   In `CredentialExchangeImportManagerImpl`, the `for` loop used for importing CXF payloads has been replaced with a more idiomatic Kotlin `flatMap` operation. This simplifies the code by removing the mutable list and the explicit fold operation.
*   In `CredentialExchangePayloadParserImpl`, an error log using `Timber.e` has been added to the catch block for `SerializationException`. This will log when the app fails to serialize received accounts during the credential exchange process, aiding in future debugging efforts.
@SaintPatrck
Copy link
Contributor Author

Thanks @david-livefront

@SaintPatrck SaintPatrck added this pull request to the merge queue Feb 17, 2026
Merged via the queue into main with commit 9b13cd4 Feb 17, 2026
28 checks passed
@SaintPatrck SaintPatrck deleted the cred-exchange/dep-updates branch February 17, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review app:authenticator Bitwarden Authenticator app context app:password-manager Bitwarden Password Manager app context t:deps Change Type - Dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants