Skip to content

Commit 606dd73

Browse files
committed
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 606dd73

File tree

4 files changed

+506
-0
lines changed

4 files changed

+506
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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+
207+
String registeredClientId = rs.getString("registered_client_id");
208+
209+
RegisteredClient registeredClient = this.registeredClientRepository
210+
.findById(registeredClientId);
211+
if (registeredClient == null) {
212+
throw new DataRetrievalFailureException(
213+
"The RegisteredClient with id '" + registeredClientId + "' it was not found in the RegisteredClientRepository.");
214+
}
215+
216+
String principalName = rs.getString("principal_name");
217+
218+
OAuth2AuthorizationConsent.Builder builder = OAuth2AuthorizationConsent.withId(registeredClientId, principalName);
219+
String authorizationConsentAuthorities = rs.getString("authorities");
220+
if (authorizationConsentAuthorities != null) {
221+
for (String authority : StringUtils.commaDelimitedListToSet(authorizationConsentAuthorities)) {
222+
builder.authority(new SimpleGrantedAuthority(authority));
223+
}
224+
}
225+
return builder.build();
226+
}
227+
}
228+
229+
/**
230+
* The default {@code Function} that maps {@link OAuth2AuthorizationConsent} to a
231+
* {@code List} of {@link SqlParameterValue}.
232+
*/
233+
public static class OAuth2AuthorizationConsentParametersMapper implements Function<OAuth2AuthorizationConsent, List<SqlParameterValue>> {
234+
235+
@Override
236+
public List<SqlParameterValue> apply(OAuth2AuthorizationConsent authorizationConsent) {
237+
238+
List<SqlParameterValue> parameters = new ArrayList<>();
239+
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getRegisteredClientId()));
240+
parameters.add(new SqlParameterValue(Types.VARCHAR, authorizationConsent.getPrincipalName()));
241+
242+
Set<String> authorities = new HashSet<>();
243+
for (GrantedAuthority authority : authorizationConsent.getAuthorities()) {
244+
authorities.add(authority.getAuthority());
245+
}
246+
parameters.add(new SqlParameterValue(Types.VARCHAR, StringUtils.collectionToDelimitedString(authorities, ",")));
247+
return parameters;
248+
}
249+
}
250+
251+
}

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

Lines changed: 20 additions & 0 deletions
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)