|  | @@ -0,0 +1,554 @@
 | 
	
		
			
				|  |  | +/*
 | 
	
		
			
				|  |  | + * Copyright 2020-2021 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.
 | 
	
		
			
				|  |  | + * You may obtain a copy of the License at
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + *      https://www.apache.org/licenses/LICENSE-2.0
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * Unless required by applicable law or agreed to in writing, software
 | 
	
		
			
				|  |  | + * distributed under the License is distributed on an "AS IS" BASIS,
 | 
	
		
			
				|  |  | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
	
		
			
				|  |  | + * See the License for the specific language governing permissions and
 | 
	
		
			
				|  |  | + * limitations under the License.
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +package org.springframework.security.oauth2.server.authorization;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import com.fasterxml.jackson.core.JsonProcessingException;
 | 
	
		
			
				|  |  | +import com.fasterxml.jackson.databind.ObjectMapper;
 | 
	
		
			
				|  |  | +import org.springframework.dao.DataRetrievalFailureException;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.core.ArgumentPreparedStatementSetter;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.core.JdbcOperations;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.core.PreparedStatementSetter;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.core.RowMapper;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.core.SqlParameterValue;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.support.lob.DefaultLobHandler;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.support.lob.LobCreator;
 | 
	
		
			
				|  |  | +import org.springframework.jdbc.support.lob.LobHandler;
 | 
	
		
			
				|  |  | +import org.springframework.lang.Nullable;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.AbstractOAuth2Token;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.AuthorizationGrantType;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.OAuth2AccessToken;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.OAuth2TokenType;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.core.oidc.OidcIdToken;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 | 
	
		
			
				|  |  | +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 | 
	
		
			
				|  |  | +import org.springframework.util.Assert;
 | 
	
		
			
				|  |  | +import org.springframework.util.CollectionUtils;
 | 
	
		
			
				|  |  | +import org.springframework.util.StringUtils;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import java.nio.charset.StandardCharsets;
 | 
	
		
			
				|  |  | +import java.sql.PreparedStatement;
 | 
	
		
			
				|  |  | +import java.sql.ResultSet;
 | 
	
		
			
				|  |  | +import java.sql.SQLException;
 | 
	
		
			
				|  |  | +import java.sql.Timestamp;
 | 
	
		
			
				|  |  | +import java.sql.Types;
 | 
	
		
			
				|  |  | +import java.time.Instant;
 | 
	
		
			
				|  |  | +import java.util.ArrayList;
 | 
	
		
			
				|  |  | +import java.util.Collections;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  | +import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.Set;
 | 
	
		
			
				|  |  | +import java.util.function.Function;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * A JDBC implementation of an {@link OAuth2AuthorizationService} that uses a
 | 
	
		
			
				|  |  | + * <p>
 | 
	
		
			
				|  |  | + * {@link JdbcOperations} for {@link OAuth2Authorization} persistence.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * <p>
 | 
	
		
			
				|  |  | + * <b>NOTE:</b> This {@code OAuth2AuthorizationService} depends on the table definition
 | 
	
		
			
				|  |  | + * described in
 | 
	
		
			
				|  |  | + * "classpath:org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql" and
 | 
	
		
			
				|  |  | + * therefore MUST be defined in the database schema.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * @author Ovidiu Popa
 | 
	
		
			
				|  |  | + * @see OAuth2AuthorizationService
 | 
	
		
			
				|  |  | + * @see OAuth2Authorization
 | 
	
		
			
				|  |  | + * @see JdbcOperations
 | 
	
		
			
				|  |  | + * @see RowMapper
 | 
	
		
			
				|  |  | + * @since 0.1.2
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +public final class JdbcOAuth2AuthorizationService implements OAuth2AuthorizationService {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// @formatter:off
 | 
	
		
			
				|  |  | +	private static final String COLUMN_NAMES = "id, "
 | 
	
		
			
				|  |  | +			+ "registered_client_id, "
 | 
	
		
			
				|  |  | +			+ "principal_name, "
 | 
	
		
			
				|  |  | +			+ "authorization_grant_type, "
 | 
	
		
			
				|  |  | +			+ "attributes, "
 | 
	
		
			
				|  |  | +			+ "state, "
 | 
	
		
			
				|  |  | +			+ "authorization_code_value, "
 | 
	
		
			
				|  |  | +			+ "authorization_code_issued_at, "
 | 
	
		
			
				|  |  | +			+ "authorization_code_expires_at,"
 | 
	
		
			
				|  |  | +			+ "authorization_code_metadata,"
 | 
	
		
			
				|  |  | +			+ "access_token_value,"
 | 
	
		
			
				|  |  | +			+ "access_token_issued_at,"
 | 
	
		
			
				|  |  | +			+ "access_token_expires_at,"
 | 
	
		
			
				|  |  | +			+ "access_token_metadata,"
 | 
	
		
			
				|  |  | +			+ "access_token_type,"
 | 
	
		
			
				|  |  | +			+ "access_token_scopes,"
 | 
	
		
			
				|  |  | +			+ "oidc_id_token_value,"
 | 
	
		
			
				|  |  | +			+ "oidc_id_token_issued_at,"
 | 
	
		
			
				|  |  | +			+ "oidc_id_token_expires_at,"
 | 
	
		
			
				|  |  | +			+ "oidc_id_token_metadata,"
 | 
	
		
			
				|  |  | +			+ "refresh_token_value,"
 | 
	
		
			
				|  |  | +			+ "refresh_token_issued_at,"
 | 
	
		
			
				|  |  | +			+ "refresh_token_expires_at,"
 | 
	
		
			
				|  |  | +			+ "refresh_token_metadata";
 | 
	
		
			
				|  |  | +	// @formatter:on
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static final String TABLE_NAME = "oauth2_authorization";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static final String PK_FILTER = "id = ?";
 | 
	
		
			
				|  |  | +	private static final String UNKNOWN_TOKEN_TYPE_FILTER = "state = ? OR authorization_code_value = ? OR " +
 | 
	
		
			
				|  |  | +			"access_token_value = ? OR " +
 | 
	
		
			
				|  |  | +			"refresh_token_value = ?";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static final String STATE_FILTER = "state = ?";
 | 
	
		
			
				|  |  | +	private static final String AUTHORIZATION_CODE_FILTER = "authorization_code_value = ?";
 | 
	
		
			
				|  |  | +	private static final String ACCESS_TOKEN_FILTER = "access_token_value = ?";
 | 
	
		
			
				|  |  | +	private static final String REFRESH_TOKEN_FILTER = "refresh_token_value = ?";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// @formatter:off
 | 
	
		
			
				|  |  | +	private static final String LOAD_AUTHORIZATION_SQL = "SELECT " + COLUMN_NAMES
 | 
	
		
			
				|  |  | +			+ " FROM " + TABLE_NAME
 | 
	
		
			
				|  |  | +			+ " WHERE ";
 | 
	
		
			
				|  |  | +	// @formatter:on
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// @formatter:off
 | 
	
		
			
				|  |  | +	private static final String SAVE_AUTHORIZATION_SQL = "INSERT INTO " + TABLE_NAME
 | 
	
		
			
				|  |  | +			+ " (" + COLUMN_NAMES + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?)";
 | 
	
		
			
				|  |  | +	// @formatter:on
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// @formatter:off
 | 
	
		
			
				|  |  | +	private static final String UPDATE_AUTHORIZATION_SQL = "UPDATE " + TABLE_NAME
 | 
	
		
			
				|  |  | +			+ " SET registered_client_id = ?, principal_name = ?, authorization_grant_type = ?, attributes = ?, state = ?,"
 | 
	
		
			
				|  |  | +			+ " authorization_code_value = ?, authorization_code_issued_at = ?, authorization_code_expires_at = ?, authorization_code_metadata = ?,"
 | 
	
		
			
				|  |  | +			+ " access_token_value = ?, access_token_issued_at = ?, access_token_expires_at = ?, access_token_metadata = ?, access_token_type = ?, access_token_scopes = ?,"
 | 
	
		
			
				|  |  | +			+ " oidc_id_token_value = ?, oidc_id_token_issued_at = ?, oidc_id_token_expires_at = ?, oidc_id_token_metadata = ?,"
 | 
	
		
			
				|  |  | +			+ " refresh_token_value = ?, refresh_token_issued_at = ?, refresh_token_expires_at = ?, refresh_token_metadata = ?"
 | 
	
		
			
				|  |  | +			+ " WHERE " + PK_FILTER;
 | 
	
		
			
				|  |  | +	// @formatter:on
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static final String REMOVE_AUTHORIZATION_SQL = "DELETE FROM " + TABLE_NAME + " WHERE " + PK_FILTER;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private final JdbcOperations jdbcOperations;
 | 
	
		
			
				|  |  | +	private final LobHandler lobHandler;
 | 
	
		
			
				|  |  | +	private RowMapper<OAuth2Authorization> authorizationRowMapper;
 | 
	
		
			
				|  |  | +	private Function<OAuth2Authorization, List<SqlParameterValue>> authorizationParametersMapper;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Constructs a {@code JdbcOAuth2AuthorizationService} using the provided parameters.
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param jdbcOperations             the JDBC operations
 | 
	
		
			
				|  |  | +	 * @param registeredClientRepository the registered client repository
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public JdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations,
 | 
	
		
			
				|  |  | +			RegisteredClientRepository registeredClientRepository) {
 | 
	
		
			
				|  |  | +		this(jdbcOperations, registeredClientRepository, new DefaultLobHandler());
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Constructs a {@code JdbcOAuth2AuthorizationService} using the provided parameters.
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param jdbcOperations             the JDBC operations
 | 
	
		
			
				|  |  | +	 * @param registeredClientRepository the registered client repository
 | 
	
		
			
				|  |  | +	 * @param lobHandler                 the handler for large binary fields and large text fields
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public JdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations,
 | 
	
		
			
				|  |  | +			RegisteredClientRepository registeredClientRepository, LobHandler lobHandler) {
 | 
	
		
			
				|  |  | +		this(jdbcOperations, registeredClientRepository, lobHandler, new ObjectMapper());
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Constructs a {@code JdbcOAuth2AuthorizationService} using the provided parameters.
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param jdbcOperations             the JDBC operations
 | 
	
		
			
				|  |  | +	 * @param registeredClientRepository the registered client repository
 | 
	
		
			
				|  |  | +	 * @param lobHandler                 the handler for large binary fields and large text fields
 | 
	
		
			
				|  |  | +	 * @param objectMapper               the object mapper
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public JdbcOAuth2AuthorizationService(JdbcOperations jdbcOperations,
 | 
	
		
			
				|  |  | +			RegisteredClientRepository registeredClientRepository,
 | 
	
		
			
				|  |  | +			LobHandler lobHandler, ObjectMapper objectMapper) {
 | 
	
		
			
				|  |  | +		Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
 | 
	
		
			
				|  |  | +		Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
 | 
	
		
			
				|  |  | +		Assert.notNull(lobHandler, "lobHandler cannot be null");
 | 
	
		
			
				|  |  | +		Assert.notNull(objectMapper, "objectMapper cannot be null");
 | 
	
		
			
				|  |  | +		this.jdbcOperations = jdbcOperations;
 | 
	
		
			
				|  |  | +		this.lobHandler = lobHandler;
 | 
	
		
			
				|  |  | +		OAuth2AuthorizationRowMapper authorizationRowMapper = new OAuth2AuthorizationRowMapper(registeredClientRepository, objectMapper);
 | 
	
		
			
				|  |  | +		authorizationRowMapper.setLobHandler(lobHandler);
 | 
	
		
			
				|  |  | +		this.authorizationRowMapper = authorizationRowMapper;
 | 
	
		
			
				|  |  | +		this.authorizationParametersMapper = new OAuth2AuthorizationParametersMapper(objectMapper);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Override
 | 
	
		
			
				|  |  | +	public void save(OAuth2Authorization authorization) {
 | 
	
		
			
				|  |  | +		Assert.notNull(authorization, "authorization cannot be null");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		OAuth2Authorization existingAuthorization = findById(authorization.getId());
 | 
	
		
			
				|  |  | +		if (existingAuthorization == null) {
 | 
	
		
			
				|  |  | +			insertAuthorization(authorization);
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  | +			updateAuthorization(authorization);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private void updateAuthorization(OAuth2Authorization authorization) {
 | 
	
		
			
				|  |  | +		List<SqlParameterValue> parameters = this.authorizationParametersMapper.apply(authorization);
 | 
	
		
			
				|  |  | +		SqlParameterValue id = parameters.remove(0);
 | 
	
		
			
				|  |  | +		parameters.add(id);
 | 
	
		
			
				|  |  | +		try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
 | 
	
		
			
				|  |  | +			PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator,
 | 
	
		
			
				|  |  | +					parameters.toArray());
 | 
	
		
			
				|  |  | +			this.jdbcOperations.update(UPDATE_AUTHORIZATION_SQL, pss);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private void insertAuthorization(OAuth2Authorization authorization) {
 | 
	
		
			
				|  |  | +		List<SqlParameterValue> parameters = this.authorizationParametersMapper.apply(authorization);
 | 
	
		
			
				|  |  | +		try (LobCreator lobCreator = this.lobHandler.getLobCreator()) {
 | 
	
		
			
				|  |  | +			PreparedStatementSetter pss = new LobCreatorArgumentPreparedStatementSetter(lobCreator,
 | 
	
		
			
				|  |  | +					parameters.toArray());
 | 
	
		
			
				|  |  | +			this.jdbcOperations.update(SAVE_AUTHORIZATION_SQL, pss);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Override
 | 
	
		
			
				|  |  | +	public void remove(OAuth2Authorization authorization) {
 | 
	
		
			
				|  |  | +		Assert.notNull(authorization, "authorization cannot be null");
 | 
	
		
			
				|  |  | +		SqlParameterValue[] parameters = new SqlParameterValue[]{
 | 
	
		
			
				|  |  | +				new SqlParameterValue(Types.VARCHAR, authorization.getId())
 | 
	
		
			
				|  |  | +		};
 | 
	
		
			
				|  |  | +		PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
 | 
	
		
			
				|  |  | +		this.jdbcOperations.update(REMOVE_AUTHORIZATION_SQL, pss);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Nullable
 | 
	
		
			
				|  |  | +	@Override
 | 
	
		
			
				|  |  | +	public OAuth2Authorization findById(String id) {
 | 
	
		
			
				|  |  | +		Assert.hasText(id, "id cannot be empty");
 | 
	
		
			
				|  |  | +		List<SqlParameterValue> parameters = new ArrayList<>();
 | 
	
		
			
				|  |  | +		parameters.add(new SqlParameterValue(Types.VARCHAR, id));
 | 
	
		
			
				|  |  | +		return findBy(PK_FILTER, parameters);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Nullable
 | 
	
		
			
				|  |  | +	@Override
 | 
	
		
			
				|  |  | +	public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
 | 
	
		
			
				|  |  | +		Assert.hasText(token, "token cannot be empty");
 | 
	
		
			
				|  |  | +		List<SqlParameterValue> parameters = new ArrayList<>();
 | 
	
		
			
				|  |  | +		if (tokenType == null) {
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.VARCHAR, token));
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)));
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)));
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)));
 | 
	
		
			
				|  |  | +			return findBy(UNKNOWN_TOKEN_TYPE_FILTER, parameters);
 | 
	
		
			
				|  |  | +		} else if (OAuth2ParameterNames.STATE.equals(tokenType.getValue())) {
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.VARCHAR, token));
 | 
	
		
			
				|  |  | +			return findBy(STATE_FILTER, parameters);
 | 
	
		
			
				|  |  | +		} else if (OAuth2ParameterNames.CODE.equals(tokenType.getValue())) {
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)));
 | 
	
		
			
				|  |  | +			return findBy(AUTHORIZATION_CODE_FILTER, parameters);
 | 
	
		
			
				|  |  | +		} else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) {
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)));
 | 
	
		
			
				|  |  | +			return findBy(ACCESS_TOKEN_FILTER, parameters);
 | 
	
		
			
				|  |  | +		} else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) {
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, token.getBytes(StandardCharsets.UTF_8)));
 | 
	
		
			
				|  |  | +			return findBy(REFRESH_TOKEN_FILTER, parameters);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +		return null;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private OAuth2Authorization findBy(String filter, List<SqlParameterValue> parameters) {
 | 
	
		
			
				|  |  | +		PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
 | 
	
		
			
				|  |  | +		List<OAuth2Authorization> result = this.jdbcOperations.query(LOAD_AUTHORIZATION_SQL + filter, pss, this.authorizationRowMapper);
 | 
	
		
			
				|  |  | +		return !result.isEmpty() ? result.get(0) : null;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Sets the {@link RowMapper} used for mapping the current row in
 | 
	
		
			
				|  |  | +	 * {@code java.sql.ResultSet} to {@link OAuth2Authorization}. The default is
 | 
	
		
			
				|  |  | +	 * {@link OAuth2AuthorizationRowMapper}.
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param authorizationRowMapper the {@link RowMapper} used for mapping the current
 | 
	
		
			
				|  |  | +	 *                               row in {@code ResultSet} to {@link OAuth2Authorization}
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public void setAuthorizationRowMapper(RowMapper<OAuth2Authorization> authorizationRowMapper) {
 | 
	
		
			
				|  |  | +		Assert.notNull(authorizationRowMapper, "authorizationRowMapper cannot be null");
 | 
	
		
			
				|  |  | +		this.authorizationRowMapper = authorizationRowMapper;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * Sets the {@code Function} used for mapping {@link OAuth2Authorization} to
 | 
	
		
			
				|  |  | +	 * a {@code List} of {@link SqlParameterValue}. The default is
 | 
	
		
			
				|  |  | +	 * {@link OAuth2AuthorizationParametersMapper}.
 | 
	
		
			
				|  |  | +	 *
 | 
	
		
			
				|  |  | +	 * @param authorizationParametersMapper the {@code Function} used for mapping
 | 
	
		
			
				|  |  | +	 *                                      {@link OAuth2Authorization} to a {@code List} of {@link SqlParameterValue}
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public void setAuthorizationParametersMapper(
 | 
	
		
			
				|  |  | +			Function<OAuth2Authorization, List<SqlParameterValue>> authorizationParametersMapper) {
 | 
	
		
			
				|  |  | +		Assert.notNull(authorizationParametersMapper, "authorizationParametersMapper cannot be null");
 | 
	
		
			
				|  |  | +		this.authorizationParametersMapper = authorizationParametersMapper;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * The default {@link RowMapper} that maps the current row in
 | 
	
		
			
				|  |  | +	 * {@code java.sql.ResultSet} to {@link OAuth2Authorization}.
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public static class OAuth2AuthorizationRowMapper implements RowMapper<OAuth2Authorization> {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		private final RegisteredClientRepository registeredClientRepository;
 | 
	
		
			
				|  |  | +		private final ObjectMapper objectMapper;
 | 
	
		
			
				|  |  | +		private LobHandler lobHandler = new DefaultLobHandler();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public OAuth2AuthorizationRowMapper(RegisteredClientRepository registeredClientRepository, ObjectMapper objectMapper) {
 | 
	
		
			
				|  |  | +			Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
 | 
	
		
			
				|  |  | +			Assert.notNull(objectMapper, "objectMapper cannot be null");
 | 
	
		
			
				|  |  | +			this.registeredClientRepository = registeredClientRepository;
 | 
	
		
			
				|  |  | +			this.objectMapper = objectMapper;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		@Override
 | 
	
		
			
				|  |  | +		@SuppressWarnings("unchecked")
 | 
	
		
			
				|  |  | +		public OAuth2Authorization mapRow(ResultSet rs, int rowNum) throws SQLException {
 | 
	
		
			
				|  |  | +			try {
 | 
	
		
			
				|  |  | +				String registeredClientId = rs.getString("registered_client_id");
 | 
	
		
			
				|  |  | +				RegisteredClient registeredClient = this.registeredClientRepository
 | 
	
		
			
				|  |  | +						.findById(registeredClientId);
 | 
	
		
			
				|  |  | +				if (registeredClient == null) {
 | 
	
		
			
				|  |  | +					throw new DataRetrievalFailureException(
 | 
	
		
			
				|  |  | +							"The RegisteredClient with id '" + registeredClientId + "' it was not found in the RegisteredClientRepository.");
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient);
 | 
	
		
			
				|  |  | +				String id = rs.getString("id");
 | 
	
		
			
				|  |  | +				String principalName = rs.getString("principal_name");
 | 
	
		
			
				|  |  | +				String authorizationGrantType = rs.getString("authorization_grant_type");
 | 
	
		
			
				|  |  | +				Map<String, Object> attributes = this.objectMapper.readValue(rs.getString("attributes"), Map.class);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				builder.id(id)
 | 
	
		
			
				|  |  | +						.principalName(principalName)
 | 
	
		
			
				|  |  | +						.authorizationGrantType(new AuthorizationGrantType(authorizationGrantType))
 | 
	
		
			
				|  |  | +						.attributes(attrs -> attrs.putAll(attributes));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				String state = rs.getString("state");
 | 
	
		
			
				|  |  | +				if (StringUtils.hasText(state)) {
 | 
	
		
			
				|  |  | +					builder.attribute(OAuth2ParameterNames.STATE, state);
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				String tokenValue;
 | 
	
		
			
				|  |  | +				Instant tokenIssuedAt;
 | 
	
		
			
				|  |  | +				Instant tokenExpiresAt;
 | 
	
		
			
				|  |  | +				byte[] authorizationCodeValue = this.lobHandler.getBlobAsBytes(rs, "authorization_code_value");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if (authorizationCodeValue != null) {
 | 
	
		
			
				|  |  | +					tokenValue = new String(authorizationCodeValue,
 | 
	
		
			
				|  |  | +							StandardCharsets.UTF_8);
 | 
	
		
			
				|  |  | +					tokenIssuedAt = rs.getTimestamp("authorization_code_issued_at").toInstant();
 | 
	
		
			
				|  |  | +					tokenExpiresAt = rs.getTimestamp("authorization_code_expires_at").toInstant();
 | 
	
		
			
				|  |  | +					Map<String, Object> authorizationCodeMetadata = this.objectMapper.readValue(rs.getString("authorization_code_metadata"), Map.class);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
 | 
	
		
			
				|  |  | +							tokenValue, tokenIssuedAt, tokenExpiresAt);
 | 
	
		
			
				|  |  | +					builder
 | 
	
		
			
				|  |  | +							.token(authorizationCode, (metadata) -> metadata.putAll(authorizationCodeMetadata));
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				byte[] accessTokenValue = this.lobHandler.getBlobAsBytes(rs, "access_token_value");
 | 
	
		
			
				|  |  | +				if (accessTokenValue != null) {
 | 
	
		
			
				|  |  | +					tokenValue = new String(accessTokenValue,
 | 
	
		
			
				|  |  | +							StandardCharsets.UTF_8);
 | 
	
		
			
				|  |  | +					tokenIssuedAt = rs.getTimestamp("access_token_issued_at").toInstant();
 | 
	
		
			
				|  |  | +					tokenExpiresAt = rs.getTimestamp("access_token_expires_at").toInstant();
 | 
	
		
			
				|  |  | +					Map<String, Object> accessTokenMetadata = this.objectMapper.readValue(rs.getString("access_token_metadata"), Map.class);
 | 
	
		
			
				|  |  | +					OAuth2AccessToken.TokenType tokenType = null;
 | 
	
		
			
				|  |  | +					if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) {
 | 
	
		
			
				|  |  | +						tokenType = OAuth2AccessToken.TokenType.BEARER;
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					Set<String> scopes = Collections.emptySet();
 | 
	
		
			
				|  |  | +					String accessTokenScopes = rs.getString("access_token_scopes");
 | 
	
		
			
				|  |  | +					if (accessTokenScopes != null) {
 | 
	
		
			
				|  |  | +						scopes = StringUtils.commaDelimitedListToSet(accessTokenScopes);
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +					OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, tokenValue, tokenIssuedAt, tokenExpiresAt, scopes);
 | 
	
		
			
				|  |  | +					builder
 | 
	
		
			
				|  |  | +							.token(accessToken, (metadata) -> metadata.putAll(accessTokenMetadata));
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				byte[] oidcIdTokenValue = this.lobHandler.getBlobAsBytes(rs, "oidc_id_token_value");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if (oidcIdTokenValue != null) {
 | 
	
		
			
				|  |  | +					tokenValue = new String(oidcIdTokenValue,
 | 
	
		
			
				|  |  | +							StandardCharsets.UTF_8);
 | 
	
		
			
				|  |  | +					tokenIssuedAt = rs.getTimestamp("oidc_id_token_issued_at").toInstant();
 | 
	
		
			
				|  |  | +					tokenExpiresAt = rs.getTimestamp("oidc_id_token_expires_at").toInstant();
 | 
	
		
			
				|  |  | +					Map<String, Object> oidcTokenMetadata = this.objectMapper.readValue(rs.getString("oidc_id_token_metadata"), Map.class);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					OidcIdToken oidcToken = new OidcIdToken(
 | 
	
		
			
				|  |  | +							tokenValue, tokenIssuedAt, tokenExpiresAt, (Map<String, Object>) oidcTokenMetadata.get(OAuth2Authorization.Token.CLAIMS_METADATA_NAME));
 | 
	
		
			
				|  |  | +					builder
 | 
	
		
			
				|  |  | +							.token(oidcToken, (metadata) -> metadata.putAll(oidcTokenMetadata));
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				byte[] refreshTokenValue = this.lobHandler.getBlobAsBytes(rs, "refresh_token_value");
 | 
	
		
			
				|  |  | +				if (refreshTokenValue != null) {
 | 
	
		
			
				|  |  | +					tokenValue = new String(refreshTokenValue,
 | 
	
		
			
				|  |  | +							StandardCharsets.UTF_8);
 | 
	
		
			
				|  |  | +					tokenIssuedAt = rs.getTimestamp("refresh_token_issued_at").toInstant();
 | 
	
		
			
				|  |  | +					tokenExpiresAt = null;
 | 
	
		
			
				|  |  | +					Timestamp refreshTokenExpiresAt = rs.getTimestamp("refresh_token_expires_at");
 | 
	
		
			
				|  |  | +					if (refreshTokenExpiresAt != null) {
 | 
	
		
			
				|  |  | +						tokenExpiresAt = refreshTokenExpiresAt.toInstant();
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +					Map<String, Object> refreshTokenMetadata = this.objectMapper.readValue(rs.getString("refresh_token_metadata"), Map.class);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +					OAuth2RefreshToken refreshToken = new OAuth2RefreshToken2(
 | 
	
		
			
				|  |  | +							tokenValue, tokenIssuedAt, tokenExpiresAt);
 | 
	
		
			
				|  |  | +					builder
 | 
	
		
			
				|  |  | +							.token(refreshToken, (metadata) -> metadata.putAll(refreshTokenMetadata));
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				return builder.build();
 | 
	
		
			
				|  |  | +			} catch (JsonProcessingException e) {
 | 
	
		
			
				|  |  | +				throw new IllegalArgumentException(e.getMessage(), e);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public final void setLobHandler(LobHandler lobHandler) {
 | 
	
		
			
				|  |  | +			Assert.notNull(lobHandler, "lobHandler cannot be null");
 | 
	
		
			
				|  |  | +			this.lobHandler = lobHandler;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	/**
 | 
	
		
			
				|  |  | +	 * The default {@code Function} that maps {@link OAuth2Authorization} to a
 | 
	
		
			
				|  |  | +	 * {@code List} of {@link SqlParameterValue}.
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	public static class OAuth2AuthorizationParametersMapper implements Function<OAuth2Authorization, List<SqlParameterValue>> {
 | 
	
		
			
				|  |  | +		private final ObjectMapper objectMapper;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public OAuth2AuthorizationParametersMapper(ObjectMapper objectMapper) {
 | 
	
		
			
				|  |  | +			Assert.notNull(objectMapper, "objectMapper cannot be null");
 | 
	
		
			
				|  |  | +			this.objectMapper = objectMapper;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		@Override
 | 
	
		
			
				|  |  | +		public List<SqlParameterValue> apply(OAuth2Authorization authorization) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			try {
 | 
	
		
			
				|  |  | +				List<SqlParameterValue> parameters = new ArrayList<>();
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getId()));
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getRegisteredClientId()));
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getPrincipalName()));
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, authorization.getAuthorizationGrantType().getValue()));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				String attributes = this.objectMapper.writeValueAsString(authorization.getAttributes());
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, attributes));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				String state = null;
 | 
	
		
			
				|  |  | +				String authorizationState = authorization.getAttribute(OAuth2ParameterNames.STATE);
 | 
	
		
			
				|  |  | +				if (StringUtils.hasText(authorizationState)) {
 | 
	
		
			
				|  |  | +					state = authorizationState;
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, state));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode =
 | 
	
		
			
				|  |  | +						authorization.getToken(OAuth2AuthorizationCode.class);
 | 
	
		
			
				|  |  | +				List<SqlParameterValue> authorizationCodeSqlParameters = toSqlParameterList(authorizationCode);
 | 
	
		
			
				|  |  | +				parameters.addAll(authorizationCodeSqlParameters);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				OAuth2Authorization.Token<OAuth2AccessToken> accessToken =
 | 
	
		
			
				|  |  | +						authorization.getToken(OAuth2AccessToken.class);
 | 
	
		
			
				|  |  | +				List<SqlParameterValue> accessTokenSqlParameters = toSqlParameterList(accessToken);
 | 
	
		
			
				|  |  | +				parameters.addAll(accessTokenSqlParameters);
 | 
	
		
			
				|  |  | +				String accessTokenType = null;
 | 
	
		
			
				|  |  | +				String accessTokenScopes = null;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if (accessToken != null) {
 | 
	
		
			
				|  |  | +					accessTokenType = accessToken.getToken().getTokenType().getValue();
 | 
	
		
			
				|  |  | +					if (!CollectionUtils.isEmpty(accessToken.getToken().getScopes())) {
 | 
	
		
			
				|  |  | +						accessTokenScopes = StringUtils.collectionToDelimitedString(accessToken.getToken().getScopes(), ",");
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenType));
 | 
	
		
			
				|  |  | +				parameters.add(new SqlParameterValue(Types.VARCHAR, accessTokenScopes));
 | 
	
		
			
				|  |  | +				OAuth2Authorization.Token<OidcIdToken> oidcIdToken = authorization.getToken(OidcIdToken.class);
 | 
	
		
			
				|  |  | +				List<SqlParameterValue> oidcTokenSqlParameters = toSqlParameterList(oidcIdToken);
 | 
	
		
			
				|  |  | +				parameters.addAll(oidcTokenSqlParameters);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				List<SqlParameterValue> refreshTokenSqlParameters = toSqlParameterList(refreshToken);
 | 
	
		
			
				|  |  | +				parameters.addAll(refreshTokenSqlParameters);
 | 
	
		
			
				|  |  | +				return parameters;
 | 
	
		
			
				|  |  | +			} catch (JsonProcessingException e) {
 | 
	
		
			
				|  |  | +				throw new IllegalArgumentException(e.getMessage(), e);
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		private <T extends AbstractOAuth2Token> List<SqlParameterValue> toSqlParameterList(OAuth2Authorization.Token<T> token) throws JsonProcessingException {
 | 
	
		
			
				|  |  | +			List<SqlParameterValue> parameters = new ArrayList<>();
 | 
	
		
			
				|  |  | +			byte[] tokenValue = null;
 | 
	
		
			
				|  |  | +			Timestamp tokenIssuedAt = null;
 | 
	
		
			
				|  |  | +			Timestamp tokenExpiresAt = null;
 | 
	
		
			
				|  |  | +			String codeMetadata = null;
 | 
	
		
			
				|  |  | +			if (token != null) {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				tokenValue = token.getToken().getTokenValue().getBytes(StandardCharsets.UTF_8);
 | 
	
		
			
				|  |  | +				if (token.getToken().getIssuedAt() != null) {
 | 
	
		
			
				|  |  | +					tokenIssuedAt = Timestamp.from(token.getToken().getIssuedAt());
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				if (token.getToken().getExpiresAt() != null) {
 | 
	
		
			
				|  |  | +					tokenExpiresAt = Timestamp.from(token.getToken().getExpiresAt());
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				codeMetadata = this.objectMapper.writeValueAsString(token.getMetadata());
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.BLOB, tokenValue));
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenIssuedAt));
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.TIMESTAMP, tokenExpiresAt));
 | 
	
		
			
				|  |  | +			parameters.add(new SqlParameterValue(Types.VARCHAR, codeMetadata));
 | 
	
		
			
				|  |  | +			return parameters;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private static final class LobCreatorArgumentPreparedStatementSetter extends ArgumentPreparedStatementSetter {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		protected final LobCreator lobCreator;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		private LobCreatorArgumentPreparedStatementSetter(LobCreator lobCreator, Object[] args) {
 | 
	
		
			
				|  |  | +			super(args);
 | 
	
		
			
				|  |  | +			this.lobCreator = lobCreator;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		@Override
 | 
	
		
			
				|  |  | +		protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException {
 | 
	
		
			
				|  |  | +			if (argValue instanceof SqlParameterValue) {
 | 
	
		
			
				|  |  | +				SqlParameterValue paramValue = (SqlParameterValue) argValue;
 | 
	
		
			
				|  |  | +				if (paramValue.getSqlType() == Types.BLOB) {
 | 
	
		
			
				|  |  | +					if (paramValue.getValue() != null) {
 | 
	
		
			
				|  |  | +						Assert.isInstanceOf(byte[].class, paramValue.getValue(),
 | 
	
		
			
				|  |  | +								"Value of blob parameter must be byte[]");
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +					byte[] valueBytes = (byte[]) paramValue.getValue();
 | 
	
		
			
				|  |  | +					this.lobCreator.setBlobAsBytes(ps, parameterPosition, valueBytes);
 | 
	
		
			
				|  |  | +					return;
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			super.doSetValue(ps, parameterPosition, argValue);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +}
 |