Skip to content
This repository has been archived by the owner on Apr 5, 2022. It is now read-only.

Added support for Redis as a backing datastore for social connections #198

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ configure(allprojects) {
]

sourceSets.test.resources.srcDirs = [
"src/test/resources",
"src/test/resources",
"src/test/java"
]

Expand Down Expand Up @@ -148,7 +148,7 @@ configure(subprojects) { subproject ->
archives javadocJar
}

configurations {
configurations {
springReleaseTestRuntime.extendsFrom testRuntime
springSnapshotTestRuntime.extendsFrom testRuntime
}
Expand Down Expand Up @@ -195,13 +195,16 @@ project("spring-social-core") {
description = "Foundational module containing the ServiceProvider Connect Framework and Service API invocation support."
dependencies {
compile("org.springframework:spring-jdbc:$springVersion", optional)
compile("org.springframework.data:spring-data-redis:$springDataRedisVersion", optional)
compile("org.springframework:spring-web:$springVersion")
compile("org.springframework.security:spring-security-crypto:$springSecurityVersion", optional)
compile("org.apache.httpcomponents:httpclient:$httpComponentsVersion", optional)
testCompile("com.h2database:h2:$h2Version")
testCompile("org.springframework:spring-test:$springVersion")
testCompile("javax.servlet:javax.servlet-api:$servletApiVersion", provided)
testCompile("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
testCompile("org.apache.commons:commons-pool2:$apacheCommonsPoolVersion")
testCompile("redis.clients:jedis:2.8.1")
}
}

Expand Down Expand Up @@ -256,7 +259,7 @@ configure(rootProject) {

dependencies { // for integration tests
}

task api(type: Javadoc) {
group = "Documentation"
description = "Generates aggregated Javadoc API documentation."
Expand Down Expand Up @@ -310,7 +313,7 @@ artifacts {
archives dist
archives project(':docs').docsZip
archives project(':docs').schemaZip
}
}

task wrapper(type: Wrapper) {
gradleVersion = "1.12"
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
h2Version=1.3.176
springSecurityVersion=3.2.9.RELEASE
springDataRedisVersion=1.7.0.RC1
apacheCommonsPoolVersion=2.2
junitVersion=4.12
httpComponentsVersion=4.3.6
aspectjVersion=1.8.5
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.springframework.social.connect.redis;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.*;
import org.springframework.social.connect.redis.data.SocialRedisConnection;
import org.springframework.social.connect.redis.data.SocialRedisConnectionRepository;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class RedisConnectionRepository implements ConnectionRepository {

private final ConnectionFactoryLocator connectionFactoryLocator;
private final TextEncryptor textEncryptor;
private final SocialRedisConnectionRepository socialRedisConnectionRepository;
private final String userId;

public RedisConnectionRepository(final ConnectionFactoryLocator connectionFactoryLocator, final TextEncryptor textEncryptor, final SocialRedisConnectionRepository socialRedisConnectionRepository, final String userId) {
Assert.notNull(socialRedisConnectionRepository, "socialRedisConnectionRepository is required");
Assert.notNull(userId, "userId is required");

this.userId = userId;
this.socialRedisConnectionRepository = socialRedisConnectionRepository;
this.connectionFactoryLocator = connectionFactoryLocator;
this.textEncryptor = textEncryptor;
}

public MultiValueMap<String, Connection<?>> findAllConnections() {
Iterable<SocialRedisConnection> allConnections = socialRedisConnectionRepository.findByUserId(userId);

final MultiValueMap<String, Connection<?>> connections = new LinkedMultiValueMap<String, Connection<?>>();
Set<String> registeredProviderIds = connectionFactoryLocator.registeredProviderIds();
for (String registeredProviderId : registeredProviderIds) {
connections.put(registeredProviderId, new ArrayList<Connection<?>>());
}

for (SocialRedisConnection connection : allConnections) {
connections.add(connection.getProviderId(), connectionMapper.mapConnection(connection));
}

return connections;
}

public List<Connection<?>> findConnections(String providerId) {
Iterable<SocialRedisConnection> connections = socialRedisConnectionRepository.findByProviderId(providerId);

List<Connection<?>> providerConnections = new ArrayList<Connection<?>>();
for (SocialRedisConnection connection : connections) {
providerConnections.add(connectionMapper.mapConnection(connection));
}

return providerConnections;
}

public <A> List<Connection<A>> findConnections(Class<A> apiType) {
List<?> connections = findConnections(getProviderId(apiType));
return (List<Connection<A>>) connections;
}

public MultiValueMap<String, Connection<?>> findConnectionsToUsers(MultiValueMap<String, String> providerUserIds) {
return null;
}

public Connection<?> getConnection(ConnectionKey connectionKey) {
try {
return connectionMapper.mapConnection(socialRedisConnectionRepository.findOneByUserIdAndProviderIdAndProviderUserId(userId, connectionKey.getProviderId(), connectionKey.getProviderUserId()));
} catch (EmptyResultDataAccessException e) {
throw new NoSuchConnectionException(connectionKey);
}
}

public <A> Connection<A> getConnection(Class<A> apiType, String providerUserId) {
String providerId = getProviderId(apiType);
return (Connection<A>) getConnection(new ConnectionKey(providerId, providerUserId));
}

public <A> Connection<A> getPrimaryConnection(Class<A> apiType) {
String providerId = getProviderId(apiType);
Connection<A> connection = (Connection<A>) findPrimaryConnection(providerId);
if (connection == null) {
throw new NotConnectedException(providerId);
}
return connection;
}

public <A> Connection<A> findPrimaryConnection(Class<A> apiType) {
String providerId = getProviderId(apiType);
return (Connection<A>) findPrimaryConnection(providerId);
}

public void addConnection(Connection<?> connection) {
try {
ConnectionData data = connection.createData();
SocialRedisConnection redisConnection = new SocialRedisConnection(data.getProviderUserId(), userId, data.getProviderId(), data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()), encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime());
socialRedisConnectionRepository.save(redisConnection);
} catch (Exception e) {
throw new DuplicateConnectionException(connection.getKey());
}
}

public void updateConnection(Connection<?> connection) {
ConnectionData data = connection.createData();
SocialRedisConnection redisConnection = socialRedisConnectionRepository.findOneByUserIdAndProviderIdAndProviderUserId(userId, data.getProviderId(), data.getProviderUserId());

redisConnection.setDisplayName(data.getDisplayName());
redisConnection.setImageUrl(data.getImageUrl());
redisConnection.setProfileUrl(data.getProfileUrl());
redisConnection.setAccessToken(encrypt(data.getAccessToken()));
redisConnection.setSecret(encrypt(data.getSecret()));
redisConnection.setRefreshToken(encrypt(data.getRefreshToken()));
redisConnection.setExpireTime(data.getExpireTime());

socialRedisConnectionRepository.save(redisConnection);
}

public void removeConnections(String providerId) {
Iterable<SocialRedisConnection> connections = socialRedisConnectionRepository.findByUserIdAndProviderId(userId, providerId);

for (SocialRedisConnection redisConnection : connections) {
socialRedisConnectionRepository.delete(redisConnection);
}
}

public void removeConnection(ConnectionKey connectionKey) {
socialRedisConnectionRepository.deleteByUserIdAndProviderIdAndProviderUserId(userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
}

private final RedisConnectionMapper connectionMapper = new RedisConnectionMapper();

private final class RedisConnectionMapper {

Connection<?> mapConnection(final SocialRedisConnection redisConnection) {
ConnectionData connectionData = mapConnectionData(redisConnection);
ConnectionFactory<?> connectionFactory = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId());
return connectionFactory.createConnection(connectionData);
}

private ConnectionData mapConnectionData(final SocialRedisConnection redisConnection) {
return new ConnectionData(redisConnection.getProviderId(), redisConnection.getProviderUserId(), redisConnection.getDisplayName(), redisConnection.getProfileUrl(), redisConnection.getImageUrl(),
decrypt(redisConnection.getAccessToken()), decrypt(redisConnection.getSecret()), decrypt(redisConnection.getRefreshToken()), redisConnection.getExpireTime());
}

private String decrypt(String encryptedText) {
return encryptedText == null ? null : textEncryptor.decrypt(encryptedText);
}
}

private Connection<?> findPrimaryConnection(String providerId) {
Iterable<SocialRedisConnection> redisConnections = socialRedisConnectionRepository.findByUserIdAndProviderId(userId, providerId);

List<Connection<?>> primaryConnections = new ArrayList<Connection<?>>();
for (SocialRedisConnection connection : redisConnections) {
primaryConnections.add(connectionMapper.mapConnection(connection));
}

if (primaryConnections.size() > 0) {
return primaryConnections.get(0);
} else {
return null;
}
}

private <A> String getProviderId(Class<A> apiType) {
return connectionFactoryLocator.getConnectionFactory(apiType).getProviderId();
}

private String encrypt(String text) {
return text == null ? null : textEncryptor.encrypt(text);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.springframework.social.connect.redis;

import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.redis.data.SocialRedisConnection;
import org.springframework.social.connect.redis.data.SocialRedisConnectionRepository;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class RedisUsersConnectionRepository implements UsersConnectionRepository {

private final ConnectionFactoryLocator connectionFactoryLocator;
private final TextEncryptor textEncryptor;
private final SocialRedisConnectionRepository socialRedisConnectionRepository;

public RedisUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator, TextEncryptor textEncryptor, SocialRedisConnectionRepository socialRedisConnectionRepository) {
Assert.notNull(connectionFactoryLocator);
Assert.notNull(textEncryptor);
Assert.notNull(socialRedisConnectionRepository);

this.connectionFactoryLocator = connectionFactoryLocator;
this.textEncryptor = textEncryptor;
this.socialRedisConnectionRepository = socialRedisConnectionRepository;
}

public List<String> findUserIdsWithConnection(final Connection<?> connection) {
String providerId = connection.getKey().getProviderId();
String providerUserId = connection.getKey().getProviderUserId();

Iterable<SocialRedisConnection> connections = socialRedisConnectionRepository.findByProviderIdAndProviderUserId(providerId, providerUserId);

List<String> userIds = new ArrayList<String>();
for (SocialRedisConnection socialRedisConnection : connections) {
userIds.add(socialRedisConnection.getUserId());
}

return userIds;
}

public Set<String> findUserIdsConnectedTo(final String providerId, final Set<String> providerUserIds) {
Set<String> userIds = new HashSet<String>();

for (String providerUserId : providerUserIds) {
for (SocialRedisConnection socialRedisConnection : socialRedisConnectionRepository.findByProviderIdAndProviderUserId(providerId, providerUserId)) {
userIds.add(socialRedisConnection.getUserId());
}
}

return userIds;
}

public ConnectionRepository createConnectionRepository(final String userId) {
return new RedisConnectionRepository(connectionFactoryLocator, textEncryptor, socialRedisConnectionRepository, userId);
}
}
Loading