Skip to content

Conversation

@SaintPatrck
Copy link
Contributor

@SaintPatrck SaintPatrck commented Nov 20, 2025

🎟️ Tracking

PM-28157
Relates to Issue #6164

📔 Objective

This PR adds support for prefixing relying party IDs (rpIds) with "www." during passkey Digital Asset Link validation to improve compatibility with web origins that use the www subdomain.

When validating passkey requests, the Android Credential Manager may receive rpIds in various formats (e.g., example.com, www.example.com, https://example.com). However, Digital Asset Link files are often hosted at https://www.example.com/.well-known/assetlinks.json. Without proper www prefixing, the validation can fail even when valid DAL relationships exist.

Changes:

  • Implements prefixWwwIfNecessaryOrNull() and prefixWwwIfNecessary() string extension functions that intelligently add "www." prefix to URIs while preserving existing schemes
  • Updates OriginManagerImpl.validateCallingApplicationAssetLinks() to apply www prefixing before https prefixing during DAL validation
  • Adds comprehensive unit test coverage for all www-prefixing scenarios

This ensures rpIds are properly normalized to https://www.{domain} format before querying Digital Asset Links, improving passkey authentication success rates for websites using www subdomains.

⏰ 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

Implements prefixWwwIfNecessaryOrNull() and prefixWwwIfNecessary()
string extension functions to ensure relying party IDs are properly
formatted with www. prefix for Digital Asset Link validation.

- Add prefixWww extension functions with KDoc
- Update OriginManager to apply www prefix before https
- Add comprehensive unit tests for www prefixing logic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@claude
Copy link
Contributor

claude bot commented Nov 20, 2025

Claude finished @SaintPatrck's task —— View job


Review Complete

Overall Assessment: APPROVE

The PR successfully implements www-prefixing for passkey Digital Asset Link validation with solid test coverage and documentation. The latest commit addresses the previously identified test coverage gap.

Status of Previous Findings

Finding 1 (⚠️ - Missing test coverage):RESOLVED
The author added three comprehensive integration tests in OriginManagerTest (lines 247-302) that verify:

  • example.comhttps://www.example.com
  • www.example.comhttps://www.example.com
  • https://example.comhttps://www.example.com

These tests properly validate the end-to-end www-prefixing behavior through the actual OriginManager flow.

Finding 2 (💭 - Security implications): No response required
The automatic www-prefixing approach is reasonable given that DAL files are cryptographically verifiable. The inline comment at OriginManagerImpl.kt:45-48 documents the rationale, which is appropriate.

Code Quality

Architecture compliance: Follows established dependency injection patterns
Code style: Matches existing prefixHttpsIfNecessary() pattern exactly
Documentation: Comprehensive KDoc with examples
Test coverage: Both unit tests (StringExtensionsTest) and integration tests (OriginManagerTest)
Error handling: Uses Result types per architecture guidelines

Minor Observations

The implementation correctly:

  • Preserves existing schemes and www prefixes
  • Uses startsWith() for scheme detection (more precise than contains())
  • Checks for "://www." to prevent double-prefixing
  • Returns null for invalid URIs in the OrNull variant

No blocking issues identified.

Comment on lines 44 to 46
sourceWebSite = relyingPartyId
.prefixWwwIfNecessary()
.prefixHttpsIfNecessary(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Missing test coverage for the primary use case

The existing OriginManagerTest only tests with DEFAULT_RELYING_PARTY_ID = "www.bitwarden.com" (line 251 in the test file), which already has the www prefix. This means the tests don't actually verify the new www-prefixing behavior works correctly.

Recommended test cases to add to OriginManagerTest
@Test
fun `validateOrigin should prefix www to rpId without www before checking asset links`() = runTest {
    coEvery {
        mockDigitalAssetLinkService.checkDigitalAssetLinksRelations(
            sourceWebSite = "https://www.example.com",  // Expected: www + https
            targetPackageName = DEFAULT_PACKAGE_NAME,
            targetCertificateFingerprint = DEFAULT_CERT_FINGERPRINT,
            relations = listOf("delegate_permission/common.handle_all_urls"),
        )
    } returns DEFAULT_ASSET_LINKS_CHECK_RESPONSE.asSuccess()

    val result = originManager.validateOrigin(
        relyingPartyId = "example.com",  // Input: no www, no https
        callingAppInfo = mockNonPrivilegedAppInfo,
    )

    assertEquals(ValidateOriginResult.Success(null), result)
}

@Test
fun `validateOrigin should preserve existing www prefix when present`() = runTest {
    coEvery {
        mockDigitalAssetLinkService.checkDigitalAssetLinksRelations(
            sourceWebSite = "https://www.example.com",
            targetPackageName = DEFAULT_PACKAGE_NAME,
            targetCertificateFingerprint = DEFAULT_CERT_FINGERPRINT,
            relations = listOf("delegate_permission/common.handle_all_urls"),
        )
    } returns DEFAULT_ASSET_LINKS_CHECK_RESPONSE.asSuccess()

    val result = originManager.validateOrigin(
        relyingPartyId = "www.example.com",  // Input: already has www
        callingAppInfo = mockNonPrivilegedAppInfo,
    )

    assertEquals(ValidateOriginResult.Success(null), result)
}

@Test
fun `validateOrigin should handle rpId with https scheme correctly`() = runTest {
    coEvery {
        mockDigitalAssetLinkService.checkDigitalAssetLinksRelations(
            sourceWebSite = "https://www.example.com",
            targetPackageName = DEFAULT_PACKAGE_NAME,
            targetCertificateFingerprint = DEFAULT_CERT_FINGERPRINT,
            relations = listOf("delegate_permission/common.handle_all_urls"),
        )
    } returns DEFAULT_ASSET_LINKS_CHECK_RESPONSE.asSuccess()

    val result = originManager.validateOrigin(
        relyingPartyId = "https://example.com",  // Input: has https but no www
        callingAppInfo = mockNonPrivilegedAppInfo,
    )

    assertEquals(ValidateOriginResult.Success(null), result)
}

These tests would verify that:

  1. example.comhttps://www.example.com
  2. www.example.comhttps://www.example.com
  3. https://example.comhttps://www.example.com

.checkDigitalAssetLinksRelations(
sourceWebSite = relyingPartyId.prefixHttpsIfNecessary(),
sourceWebSite = relyingPartyId
.prefixWwwIfNecessary()
Copy link
Contributor

Choose a reason for hiding this comment

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

💭 Consider the security implications of automatic www prefixing

While the www-prefixing improves compatibility, it's worth considering the security implications:

Current behavior: If a site hosts their Digital Asset Links file at https://www.example.com/.well-known/assetlinks.json but NOT at https://example.com/.well-known/assetlinks.json, this code will allow passkey authentication by automatically trying the www variant.

Question: Is this the intended behavior? Should we:

  1. Keep current approach (automatic fallback to www) - More permissive, better UX
  2. Try both variants (with and without www) - Most robust
  3. Only use the exact rpId provided - Most restrictive, follows spec strictly

From a security perspective, option 1 (current) seems reasonable since:

  • DAL files are cryptographically verifiable via certificate fingerprints
  • The www subdomain is typically controlled by the same entity as the apex domain
  • This aligns with how browsers handle www/non-www redirects

However, this should be documented as an intentional security decision, not just a compatibility fix.

}

else -> "www.$this"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Excellent implementation

The logic correctly handles all cases:

  • Blank/invalid URIs → null
  • Already has www. prefix → unchanged
  • Has scheme (http:// or https://) → inserts www. after scheme
  • No scheme → prepends www.

The check for "://www." prevents double-prefixing when www is already present after a scheme, which is good.

Edge case verification

Verified the logic handles these correctly:

  • "example.com""www.example.com"
  • "www.example.com""www.example.com"
  • "http://example.com""http://www.example.com"
  • "https://www.example.com""https://www.example.com"
  • ""null
  • "invalid uri"null

val invalidUri = "invalid uri"
assertNull(invalidUri.prefixWwwIfNecessaryOrNull())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 Comprehensive test coverage for string extensions

The test coverage for the new prefixWwwIfNecessaryOrNull() function is excellent:

  • ✅ No scheme, no www
  • ✅ Already has www prefix
  • ✅ HTTP scheme without www
  • ✅ HTTPS scheme without www
  • ✅ HTTP scheme with www
  • ✅ HTTPS scheme with www
  • ✅ Empty string
  • ✅ Invalid URI

All major edge cases are covered. Good work!

@github-actions
Copy link
Contributor

github-actions bot commented Nov 20, 2025

Logo
Checkmarx One – Scan Summary & Details39616998-6c78-4ca7-b1c2-49bd30622438

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

- Documented why we are injecting `www` when checking digital asset links relations.
- Added test cases for `OriginManagerImpl`.
@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

❌ Patch coverage is 90.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 85.22%. Comparing base (7918abd) to head (ecbab22).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...itwarden/ui/platform/base/util/StringExtensions.kt 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6183   +/-   ##
=======================================
  Coverage   85.22%   85.22%           
=======================================
  Files         724      724           
  Lines       53005    53014    +9     
  Branches     7705     7710    +5     
=======================================
+ Hits        45173    45181    +8     
  Misses       5143     5143           
- Partials     2689     2690    +1     

☔ 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
Copy link
Contributor Author

Thanks @david-livefront

@SaintPatrck SaintPatrck added this pull request to the merge queue Nov 20, 2025
Merged via the queue into main with commit 7b491d3 Nov 20, 2025
15 checks passed
@SaintPatrck SaintPatrck deleted the PM-28157/prefix-rpId-with-www branch November 20, 2025 16:06
SaintPatrck added a commit that referenced this pull request Nov 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants