Describe the bug
MapUserCredentialRepository.save does not remove an existing credential ID
from its previous owner's index when the same credential ID is saved with a
different userEntityUserId.
This leaves the primary credential index and the owner-to-credential-ID index
inconsistent. After deleting the credential, the old owner's index still
contains the credential ID. A subsequent findByUserId call then throws a
NullPointerException.
This is not reproducible through the standard /webauthn/register browser
registration flow because
Webauthn4JRelyingPartyOperations.registerCredential checks whether the
credential ID already exists first.
It is reproducible when application code directly calls
MapUserCredentialRepository.save with an existing credential ID and a
different owner ID.
To Reproduce
Sample repository:
https://github.com/789-yu/spring-security/tree/repro-map-user-credential-repository
Reproducer test:
https://github.com/789-yu/spring-security/blob/repro-map-user-credential-repository/webauthn/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryReproductionTests.java
Environment:
- Spring Security commit:
29f8dd53cd
- Version:
7.1.0-243-g29f8dd53cd
- JDK: 21
- Module:
spring-security-webauthn
Run:
./gradlew :spring-security-webauthn:test \
--tests org.springframework.security.web.webauthn.management.MapUserCredentialRepositoryReproductionTests \
--no-daemon \
-PtestToolchain=21 \
--rerun-tasks \
--console=plain
The reproducer performs these operations:
1. Save a credential for oldOwnerId.
2. Save the same credential ID again with newOwnerId.
3. Query the old owner.
4. Delete the credential by ID.
5. Query the old owner again.
Observed output:
1 old owner records after overwrite: [ImmutableCredentialRecord@...]
2 main index lookup from old owner credentialId: ImmutableCredentialRecord@...
3 new owner records after delete: []
4 old owner lookup after delete: java.lang.NullPointerException
The old owner's index still contains the credential ID after reassignment.
The primary index points to the new owner's record. Deleting the credential only
removes it from the new owner's index. The old owner's stale ID remains, and
findByUserId(oldOwnerId) throws a NullPointerException.
Expected behavior
When an existing credential ID is saved with a different owner, both internal
indexes should remain consistent.
The credential ID should either:
- be removed from the previous owner's index before being added to the new
owner's index; or
- be rejected if reassignment is not supported.
After deleting a credential, querying either owner should not leave a stale
credential ID or throw a NullPointerException.
**Sample**
A minimal reproducer is available here:
https://github.com/789-yu/spring-security/tree/repro-map-user-credential-repository
The reproducer test is available here:
https://github.com/789-yu/spring-security/blob/repro-map-user-credential-repository/webauthn/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryReproductionTests.java
[MapUserCredentialRepositoryReproductionTests.java](https://github.com/user-attachments/files/33189187/MapUserCredentialRepositoryReproductionTests.java)
Describe the bug
MapUserCredentialRepository.savedoes not remove an existing credential IDfrom its previous owner's index when the same credential ID is saved with a
different
userEntityUserId.This leaves the primary credential index and the owner-to-credential-ID index
inconsistent. After deleting the credential, the old owner's index still
contains the credential ID. A subsequent
findByUserIdcall then throws aNullPointerException.This is not reproducible through the standard
/webauthn/registerbrowserregistration flow because
Webauthn4JRelyingPartyOperations.registerCredentialchecks whether thecredential ID already exists first.
It is reproducible when application code directly calls
MapUserCredentialRepository.savewith an existing credential ID and adifferent owner ID.
To Reproduce
Sample repository:
https://github.com/789-yu/spring-security/tree/repro-map-user-credential-repository
Reproducer test:
https://github.com/789-yu/spring-security/blob/repro-map-user-credential-repository/webauthn/src/test/java/org/springframework/security/web/webauthn/management/MapUserCredentialRepositoryReproductionTests.java
Environment:
29f8dd53cd7.1.0-243-g29f8dd53cdspring-security-webauthnRun: