From e28b9bdf3badb3c0f33f2615fc92ea5937589d19 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Wed, 19 Feb 2025 14:43:44 +0300 Subject: [PATCH] Fix JdbcUserCredentialRepository Save Closes gh-16620 Signed-off-by: Max Batischev --- .../JdbcUserCredentialRepository.java | 38 ++++++++++++++++++- .../JdbcUserCredentialRepositoryTests.java | 24 +++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java b/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java index aa012d6964b..660daf6008e 100644 --- a/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java +++ b/web/src/main/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,6 +112,24 @@ public final class JdbcUserCredentialRepository implements UserCredentialReposit private static final String DELETE_CREDENTIAL_RECORD_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + ID_FILTER; + // @formatter:off + private static final String UPDATE_CREDENTIAL_RECORD_SQL = "UPDATE " + TABLE_NAME + + " SET user_entity_user_id = ?, " + + "public_key = ?, " + + "signature_count = ?, " + + "uv_initialized = ?, " + + "backup_eligible = ? ," + + "authenticator_transports = ?, " + + "public_key_credential_type = ?, " + + "backup_state = ?, " + + "attestation_object = ?, " + + "attestation_client_data_json = ?, " + + "created = ?, " + + "last_used = ?, " + + "label = ?" + + " WHERE " + ID_FILTER; + // @formatter:on + /** * Constructs a {@code JdbcUserCredentialRepository} using the provided parameters. * @param jdbcOperations the JDBC operations @@ -133,6 +151,13 @@ public void delete(Bytes credentialId) { @Override public void save(CredentialRecord record) { Assert.notNull(record, "record cannot be null"); + int rows = updateCredentialRecord(record); + if (rows == 0) { + insertCredentialRecord(record); + } + } + + private void insertCredentialRecord(CredentialRecord record) { List parameters = this.credentialRecordParametersMapper.apply(record); try (LobCreator lobCreator = this.lobHandler.getLobCreator()) { PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, @@ -141,6 +166,17 @@ public void save(CredentialRecord record) { } } + private int updateCredentialRecord(CredentialRecord record) { + List parameters = this.credentialRecordParametersMapper.apply(record); + SqlParameterValue credentialId = parameters.remove(0); + parameters.add(credentialId); + try (LobCreator lobCreator = this.lobHandler.getLobCreator()) { + PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator, + parameters.toArray()); + return this.jdbcOperations.update(UPDATE_CREDENTIAL_RECORD_SQL, pss); + } + } + @Override public CredentialRecord findByCredentialId(Bytes credentialId) { Assert.notNull(credentialId, "credentialId cannot be null"); diff --git a/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java b/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java index 4829b537f0a..3f495c2455b 100644 --- a/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/webauthn/management/JdbcUserCredentialRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.security.web.webauthn.api.AuthenticatorTransport; import org.springframework.security.web.webauthn.api.CredentialRecord; +import org.springframework.security.web.webauthn.api.ImmutableCredentialRecord; import org.springframework.security.web.webauthn.api.PublicKeyCredentialType; import org.springframework.security.web.webauthn.api.TestCredentialRecord; @@ -133,6 +134,27 @@ void saveCredentialRecordWhenSaveThenReturnsSaved() { assertThat(new String(savedUserCredential.getAttestationClientDataJSON().getBytes())).isEqualTo("test"); } + @Test + void saveCredentialRecordWhenRecordExistsThenReturnsUpdated() { + CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build(); + this.jdbcUserCredentialRepository.save(userCredential); + // @formatter:off + CredentialRecord updatedRecord = ImmutableCredentialRecord.fromCredentialRecord(userCredential) + .backupEligible(false) + .uvInitialized(true) + .signatureCount(200).build(); + // @formatter:on + + this.jdbcUserCredentialRepository.save(updatedRecord); + + CredentialRecord record = this.jdbcUserCredentialRepository + .findByCredentialId(userCredential.getCredentialId()); + + assertThat(record.getSignatureCount()).isEqualTo(200); + assertThat(record.isUvInitialized()).isTrue(); + assertThat(record.isBackupEligible()).isFalse(); + } + @Test void findCredentialRecordByUserIdWhenRecordExistsThenReturnsSaved() { CredentialRecord userCredential = TestCredentialRecord.fullUserCredential().build();