Skip to content

Commit 139a8a0

Browse files
Add Support JdbcPublicKeyCredentialUserEntityRepository
Closes gh-16224
1 parent 3c87692 commit 139a8a0

File tree

6 files changed

+431
-1
lines changed

6 files changed

+431
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.aot.hint;
18+
19+
import org.springframework.aot.hint.RuntimeHints;
20+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
21+
import org.springframework.jdbc.core.JdbcOperations;
22+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
23+
import org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository;
24+
25+
/**
26+
*
27+
* A JDBC implementation of an {@link PublicKeyCredentialUserEntityRepository} that uses a
28+
* {@link JdbcOperations} for {@link PublicKeyCredentialUserEntity} persistence.
29+
*
30+
* @author Max Batischev
31+
* @since 6.5
32+
*/
33+
class PublicKeyCredentialUserEntityRuntimeHints implements RuntimeHintsRegistrar {
34+
35+
@Override
36+
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
37+
hints.resources().registerPattern("org/springframework/security/user-entities-schema.sql");
38+
}
39+
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.webauthn.management;
18+
19+
import java.sql.ResultSet;
20+
import java.sql.SQLException;
21+
import java.sql.Types;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.function.Function;
25+
26+
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
27+
import org.springframework.jdbc.core.JdbcOperations;
28+
import org.springframework.jdbc.core.PreparedStatementSetter;
29+
import org.springframework.jdbc.core.RowMapper;
30+
import org.springframework.jdbc.core.SqlParameterValue;
31+
import org.springframework.security.web.webauthn.api.Bytes;
32+
import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
33+
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
34+
import org.springframework.util.Assert;
35+
36+
/**
37+
* A JDBC implementation of an {@link PublicKeyCredentialUserEntityRepository} that uses a
38+
* {@link JdbcOperations} for {@link PublicKeyCredentialUserEntity} persistence.
39+
*
40+
* <b>NOTE:</b> This {@code PublicKeyCredentialUserEntityRepository} depends on the table
41+
* definition described in
42+
* "classpath:org/springframework/security/user-entities-schema.sql" and therefore MUST be
43+
* defined in the database schema.
44+
*
45+
* @author Max Batischev
46+
* @since 6.5
47+
* @see PublicKeyCredentialUserEntityRepository
48+
* @see PublicKeyCredentialUserEntity
49+
* @see JdbcOperations
50+
* @see RowMapper
51+
*/
52+
public final class JdbcPublicKeyCredentialUserEntityRepository implements PublicKeyCredentialUserEntityRepository {
53+
54+
private RowMapper<PublicKeyCredentialUserEntity> userEntityRowMapper = new UserEntityRecordRowMapper();
55+
56+
private Function<PublicKeyCredentialUserEntity, List<SqlParameterValue>> userEntityParametersMapper = new UserEntityParametersMapper();
57+
58+
private final JdbcOperations jdbcOperations;
59+
60+
private static final String TABLE_NAME = "user_entities";
61+
62+
// @formatter:off
63+
private static final String COLUMN_NAMES = "id, "
64+
+ "name, "
65+
+ "display_name ";
66+
// @formatter:on
67+
68+
// @formatter:off
69+
private static final String SAVE_USER_SQL = "INSERT INTO " + TABLE_NAME
70+
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)";
71+
// @formatter:on
72+
73+
private static final String ID_FILTER = "id = ? ";
74+
75+
private static final String USER_NAME_FILTER = "name = ? ";
76+
77+
// @formatter:off
78+
private static final String FIND_USER_BY_ID_SQL = "SELECT " + COLUMN_NAMES
79+
+ " FROM " + TABLE_NAME
80+
+ " WHERE " + ID_FILTER;
81+
// @formatter:on
82+
83+
// @formatter:off
84+
private static final String FIND_USER_BY_NAME_SQL = "SELECT " + COLUMN_NAMES
85+
+ " FROM " + TABLE_NAME
86+
+ " WHERE " + USER_NAME_FILTER;
87+
// @formatter:on
88+
89+
private static final String DELETE_USER_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + ID_FILTER;
90+
91+
/**
92+
* Constructs a {@code JdbcPublicKeyCredentialUserEntityRepository} using the provided
93+
* parameters.
94+
* @param jdbcOperations the JDBC operations
95+
*/
96+
public JdbcPublicKeyCredentialUserEntityRepository(JdbcOperations jdbcOperations) {
97+
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
98+
this.jdbcOperations = jdbcOperations;
99+
}
100+
101+
@Override
102+
public PublicKeyCredentialUserEntity findById(Bytes id) {
103+
Assert.notNull(id, "id cannot be null");
104+
SqlParameterValue[] parameters = new SqlParameterValue[] {
105+
new SqlParameterValue(Types.VARCHAR, id.toBase64UrlString()) };
106+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
107+
List<PublicKeyCredentialUserEntity> result = this.jdbcOperations.query(FIND_USER_BY_ID_SQL, pss,
108+
this.userEntityRowMapper);
109+
return !result.isEmpty() ? result.get(0) : null;
110+
}
111+
112+
@Override
113+
public PublicKeyCredentialUserEntity findByUsername(String username) {
114+
Assert.hasText(username, "name cannot be null or empty");
115+
SqlParameterValue[] parameters = new SqlParameterValue[] { new SqlParameterValue(Types.VARCHAR, username) };
116+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
117+
List<PublicKeyCredentialUserEntity> result = this.jdbcOperations.query(FIND_USER_BY_NAME_SQL, pss,
118+
this.userEntityRowMapper);
119+
return !result.isEmpty() ? result.get(0) : null;
120+
}
121+
122+
@Override
123+
public void save(PublicKeyCredentialUserEntity userEntity) {
124+
Assert.notNull(userEntity, "userEntity cannot be null");
125+
List<SqlParameterValue> parameters = this.userEntityParametersMapper.apply(userEntity);
126+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
127+
this.jdbcOperations.update(SAVE_USER_SQL, pss);
128+
}
129+
130+
@Override
131+
public void delete(Bytes id) {
132+
Assert.notNull(id, "id cannot be null");
133+
SqlParameterValue[] parameters = new SqlParameterValue[] {
134+
new SqlParameterValue(Types.VARCHAR, id.toBase64UrlString()), };
135+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
136+
this.jdbcOperations.update(DELETE_USER_SQL, pss);
137+
}
138+
139+
private static class UserEntityParametersMapper
140+
implements Function<PublicKeyCredentialUserEntity, List<SqlParameterValue>> {
141+
142+
@Override
143+
public List<SqlParameterValue> apply(PublicKeyCredentialUserEntity userEntity) {
144+
List<SqlParameterValue> parameters = new ArrayList<>();
145+
146+
parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getId().toBase64UrlString()));
147+
parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getName()));
148+
parameters.add(new SqlParameterValue(Types.VARCHAR, userEntity.getDisplayName()));
149+
150+
return parameters;
151+
}
152+
153+
}
154+
155+
private static class UserEntityRecordRowMapper implements RowMapper<PublicKeyCredentialUserEntity> {
156+
157+
@Override
158+
public PublicKeyCredentialUserEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
159+
Bytes id = Bytes.fromBase64(new String(rs.getString("id").getBytes()));
160+
String name = rs.getString("name");
161+
String displayName = rs.getString("display_name");
162+
163+
return ImmutablePublicKeyCredentialUserEntity.builder().id(id).name(name).displayName(displayName).build();
164+
}
165+
166+
}
167+
168+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
org.springframework.aot.hint.RuntimeHintsRegistrar=\
22
org.springframework.security.web.aot.hint.WebMvcSecurityRuntimeHints,\
3-
org.springframework.security.web.aot.hint.UserCredentialRuntimeHints
3+
org.springframework.security.web.aot.hint.UserCredentialRuntimeHints,\
4+
org.springframework.security.web.aot.hint.PublicKeyCredentialUserEntityRuntimeHints
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
create table user_entities
2+
(
3+
id varchar(1000) not null,
4+
name varchar(100) not null,
5+
display_name varchar(200)
6+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.aot.hint;
18+
19+
import java.util.stream.Stream;
20+
21+
import org.junit.jupiter.api.BeforeEach;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.MethodSource;
24+
25+
import org.springframework.aot.hint.RuntimeHints;
26+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
27+
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
28+
import org.springframework.core.io.support.SpringFactoriesLoader;
29+
import org.springframework.util.ClassUtils;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
/**
34+
* Tests for {@link PublicKeyCredentialUserEntityRuntimeHints}
35+
*
36+
* @author Max Batischev
37+
*/
38+
public class PublicKeyCredentialUserEntityRuntimeHintsTests {
39+
40+
private final RuntimeHints hints = new RuntimeHints();
41+
42+
@BeforeEach
43+
void setup() {
44+
SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories")
45+
.load(RuntimeHintsRegistrar.class)
46+
.forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader()));
47+
}
48+
49+
@ParameterizedTest
50+
@MethodSource("getUserEntitiesSqlFiles")
51+
void userEntitiesSqlFilesHasHints(String schemaFile) {
52+
assertThat(RuntimeHintsPredicates.resource().forResource(schemaFile)).accepts(this.hints);
53+
}
54+
55+
private static Stream<String> getUserEntitiesSqlFiles() {
56+
return Stream.of("org/springframework/security/user-entities-schema.sql");
57+
}
58+
59+
}

0 commit comments

Comments
 (0)