Skip to content

Commit 5dbe973

Browse files
ovidiupopa07sjohnr
authored andcommitted
Provide JDBC implementation of OAuth2AuthorizationConsentService
Add new JDBC implementation of the OAuth2AuthorizationConsentService Add equals and hashCode methods in OAuth2AuthorizationConsent Closes gh-313
1 parent 8e9563a commit 5dbe973

File tree

4 files changed

+504
-0
lines changed

4 files changed

+504
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/*
2+
* Copyright 2020-2021 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+
package org.springframework.security.oauth2.server.authorization;
17+
18+
import java.sql.ResultSet;
19+
import java.sql.SQLException;
20+
import java.sql.Types;
21+
import java.util.ArrayList;
22+
import java.util.HashSet;
23+
import java.util.List;
24+
import java.util.Set;
25+
import java.util.function.Function;
26+
27+
import org.springframework.dao.DataRetrievalFailureException;
28+
import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
29+
import org.springframework.jdbc.core.JdbcOperations;
30+
import org.springframework.jdbc.core.PreparedStatementSetter;
31+
import org.springframework.jdbc.core.RowMapper;
32+
import org.springframework.jdbc.core.SqlParameterValue;
33+
import org.springframework.lang.Nullable;
34+
import org.springframework.security.core.GrantedAuthority;
35+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
36+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
37+
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
38+
import org.springframework.util.Assert;
39+
import org.springframework.util.StringUtils;
40+
41+
/**
42+
* A JDBC implementation of an {@link OAuth2AuthorizationConsentService} that uses a
43+
* <p>
44+
* {@link JdbcOperations} for {@link OAuth2AuthorizationConsent} persistence.
45+
*
46+
* <p>
47+
* <b>NOTE:</b> This {@code OAuth2AuthorizationConsentService} depends on the table definition
48+
* described in
49+
* "classpath:org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql" and
50+
* therefore MUST be defined in the database schema.
51+
*
52+
* @author Ovidiu Popa
53+
* @see OAuth2AuthorizationConsentService
54+
* @see OAuth2AuthorizationConsent
55+
* @see JdbcOperations
56+
* @see RowMapper
57+
* @since 0.1.2
58+
*/
59+
public final class JdbcOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
60+
61+
// @formatter:off
62+
private static final String COLUMN_NAMES = "registered_client_id, "
63+
+ "principal_name, "
64+
+ "authorities";
65+
// @formatter:on
66+
67+
private static final String TABLE_NAME = "oauth2_authorization_consent";
68+
69+
private static final String PK_FILTER = "registered_client_id = ? AND principal_name = ?";
70+
71+
// @formatter:off
72+
private static final String LOAD_AUTHORIZATION_CONSENT_SQL = "SELECT " + COLUMN_NAMES
73+
+ " FROM " + TABLE_NAME
74+
+ " WHERE " + PK_FILTER;
75+
// @formatter:on
76+
77+
// @formatter:off
78+
private static final String SAVE_AUTHORIZATION_CONSENT_SQL = "INSERT INTO " + TABLE_NAME
79+
+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?)";
80+
// @formatter:on
81+
82+
// @formatter:off
83+
private static final String UPDATE_AUTHORIZATION_CONSENT_SQL = "UPDATE " + TABLE_NAME
84+
+ " SET authorities = ?"
85+
+ " WHERE " + PK_FILTER;
86+
// @formatter:on
87+
88+
private static final String REMOVE_AUTHORIZATION_CONSENT_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER;
89+
90+
private final JdbcOperations jdbcOperations;
91+
private RowMapper<OAuth2AuthorizationConsent> authorizationConsentRowMapper;
92+
private Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> authorizationConsentParametersMapper;
93+
94+
/**
95+
* Constructs a {@code JdbcOAuth2AuthorizationConsentService} using the provided parameters.
96+
*
97+
* @param jdbcOperations the JDBC operations
98+
* @param registeredClientRepository the registered client repository
99+
*/
100+
public JdbcOAuth2AuthorizationConsentService(JdbcOperations jdbcOperations,
101+
RegisteredClientRepository registeredClientRepository) {
102+
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
103+
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
104+
this.jdbcOperations = jdbcOperations;
105+
this.authorizationConsentRowMapper = new OAuth2AuthorizationConsentRowMapper(registeredClientRepository);
106+
this.authorizationConsentParametersMapper = new OAuth2AuthorizationConsentParametersMapper();
107+
}
108+
109+
@Override
110+
public void save(OAuth2AuthorizationConsent authorizationConsent) {
111+
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
112+
113+
OAuth2AuthorizationConsent existingAuthorizationConsent =
114+
findById(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
115+
116+
if (existingAuthorizationConsent == null) {
117+
insertAuthorizationConsent(authorizationConsent);
118+
} else {
119+
updateAuthorizationConsent(authorizationConsent);
120+
}
121+
}
122+
123+
private void updateAuthorizationConsent(OAuth2AuthorizationConsent authorizationConsent) {
124+
List<SqlParameterValue> parameters = this.authorizationConsentParametersMapper.apply(authorizationConsent);
125+
SqlParameterValue registeredClientId = parameters.remove(0);
126+
SqlParameterValue principalName = parameters.remove(0);
127+
parameters.add(registeredClientId);
128+
parameters.add(principalName);
129+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
130+
this.jdbcOperations.update(UPDATE_AUTHORIZATION_CONSENT_SQL, pss);
131+
}
132+
133+
private void insertAuthorizationConsent(OAuth2AuthorizationConsent authorizationConsent) {
134+
List<SqlParameterValue> parameters = this.authorizationConsentParametersMapper.apply(authorizationConsent);
135+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
136+
this.jdbcOperations.update(SAVE_AUTHORIZATION_CONSENT_SQL, pss);
137+
}
138+
139+
@Override
140+
public void remove(OAuth2AuthorizationConsent authorizationConsent) {
141+
Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
142+
SqlParameterValue[] parameters = new SqlParameterValue[]{
143+
new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId()),
144+
new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName())
145+
};
146+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
147+
this.jdbcOperations.update(REMOVE_AUTHORIZATION_CONSENT_SQL, pss);
148+
}
149+
150+
@Override
151+
@Nullable
152+
public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
153+
Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
154+
Assert.hasText(principalName, "principalName cannot be empty");
155+
SqlParameterValue[] parameters = new SqlParameterValue[]{
156+
new SqlParameterValue(Types.VARCHAR, registeredClientId),
157+
new SqlParameterValue(Types.VARCHAR, principalName)};
158+
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
159+
List<OAuth2AuthorizationConsent> result = this.jdbcOperations.query(LOAD_AUTHORIZATION_CONSENT_SQL, pss,
160+
this.authorizationConsentRowMapper);
161+
return !result.isEmpty() ? result.get(0) : null;
162+
}
163+
164+
/**
165+
* Sets the {@link RowMapper} used for mapping the current row in
166+
* {@code java.sql.ResultSet} to {@link OAuth2AuthorizationConsent}. The default is
167+
* {@link OAuth2AuthorizationConsentRowMapper}.
168+
*
169+
* @param authorizationConsentRowMapper the {@link RowMapper} used for mapping the current
170+
* row in {@code ResultSet} to {@link OAuth2AuthorizationConsent}
171+
*/
172+
public void setAuthorizationConsentRowMapper(RowMapper<OAuth2AuthorizationConsent> authorizationConsentRowMapper) {
173+
Assert.notNull(authorizationConsentRowMapper, "authorizationConsentRowMapper cannot be null");
174+
this.authorizationConsentRowMapper = authorizationConsentRowMapper;
175+
}
176+
177+
/**
178+
* Sets the {@code Function} used for mapping {@link OAuth2AuthorizationConsent} to
179+
* a {@code List} of {@link SqlParameterValue}. The default is
180+
* {@link OAuth2AuthorizationConsentParametersMapper}.
181+
*
182+
* @param authorizationConsentParametersMapper the {@code Function} used for mapping
183+
* {@link OAuth2AuthorizationConsent} to a {@code List} of {@link SqlParameterValue}
184+
*/
185+
public void setAuthorizationConsentParametersMapper(
186+
Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> authorizationConsentParametersMapper) {
187+
Assert.notNull(authorizationConsentParametersMapper, "authorizationConsentParametersMapper cannot be null");
188+
this.authorizationConsentParametersMapper = authorizationConsentParametersMapper;
189+
}
190+
191+
/**
192+
* The default {@link RowMapper} that maps the current row in
193+
* {@code ResultSet} to {@link OAuth2AuthorizationConsent}.
194+
*/
195+
public static class OAuth2AuthorizationConsentRowMapper implements RowMapper<OAuth2AuthorizationConsent> {
196+
197+
private final RegisteredClientRepository registeredClientRepository;
198+
199+
public OAuth2AuthorizationConsentRowMapper(RegisteredClientRepository registeredClientRepository) {
200+
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
201+
this.registeredClientRepository = registeredClientRepository;
202+
}
203+
204+
@Override
205+
public OAuth2AuthorizationConsent mapRow(ResultSet rs, int rowNum) throws SQLException {
206+
String registeredClientId = rs.getString("registered_client_id");
207+
208+
RegisteredClient registeredClient = this.registeredClientRepository
209+
.findById(registeredClientId);
210+
if (registeredClient == null) {
211+
throw new DataRetrievalFailureException(
212+
"The RegisteredClient with id '" + registeredClientId + "' it was not found in the RegisteredClientRepository.");
213+
}
214+
215+
String principalName = rs.getString("principal_name");
216+
217+
OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(registeredClientId, principalName);
218+
String authorizationConsentAuthorities = rs.getString("authorities");
219+
if (authorizationConsentAuthorities != null) {
220+
for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsentAuthorities)) {
221+
builder.authority(new SimpleGrantedAuthority(authority));
222+
}
223+
}
224+
return builder.build();
225+
}
226+
}
227+
228+
/**
229+
* The default {@code Function} that maps {@link OAuth2AuthorizationConsent} to a
230+
* {@code List} of {@link SqlParameterValue}.
231+
*/
232+
public static class OAuth2AuthorizationConsentParametersMapper implements Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> {
233+
234+
@Override
235+
public List<SqlParameterValue> apply(OAuth2AuthorizationConsent authorizationConsent) {
236+
List<SqlParameterValue> parameters = new ArrayList<>();
237+
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId()));
238+
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName()));
239+
240+
Set<String> authorities = new HashSet<>();
241+
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) {
242+
authorities.add(authority.getAuthority());
243+
}
244+
parameters.add(new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToDelimitedString(authorities, ",")));
245+
return parameters;
246+
}
247+
}
248+
249+
}

oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationConsent.java

+20
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.Serializable;
1919
import java.util.Collections;
2020
import java.util.HashSet;
21+
import java.util.Objects;
2122
import java.util.Set;
2223
import java.util.function.Consumer;
2324
import java.util.stream.Collectors;
@@ -97,6 +98,25 @@ public Set<String> getScopes() {
9798
.collect(Collectors.toSet());
9899
}
99100

101+
@Override
102+
public boolean equals(Object obj) {
103+
if (this == obj) {
104+
return true;
105+
}
106+
if (obj == null || getClass() != obj.getClass()) {
107+
return false;
108+
}
109+
OAuth2AuthorizationConsent that = (OAuth2AuthorizationConsent) obj;
110+
return Objects.equals(this.registeredClientId, that.registeredClientId) &&
111+
Objects.equals(this.principalName, that.principalName) &&
112+
Objects.equals(this.authorities, that.authorities);
113+
}
114+
115+
@Override
116+
public int hashCode() {
117+
return Objects.hash(this.registeredClientId, this.principalName, this.authorities);
118+
}
119+
100120
/**
101121
* Returns a new {@link Builder}, initialized with the values from the provided {@code OAuth2AuthorizationConsent}.
102122
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE oauth2_authorization_consent (
2+
registered_client_id varchar(100) NOT NULL,
3+
principal_name varchar(200) NOT NULL,
4+
authorities varchar(1000) NOT NULL,
5+
PRIMARY KEY (registered_client_id, principal_name)
6+
);

0 commit comments

Comments
 (0)