/* * Copyright 2002-2014 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 * * http://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.social.connect.sqlite; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.springframework.security.crypto.encrypt.AndroidEncryptors; import org.springframework.security.crypto.encrypt.TextEncryptor; import org.springframework.security.crypto.keygen.AndroidKeyGenerators; import org.springframework.social.connect.ApiAdapter; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionData; import org.springframework.social.connect.ConnectionKey; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.ConnectionValues; import org.springframework.social.connect.DuplicateConnectionException; import org.springframework.social.connect.NoSuchConnectionException; import org.springframework.social.connect.NotConnectedException; import org.springframework.social.connect.UserProfile; import org.springframework.social.connect.UserProfileBuilder; import org.springframework.social.connect.sqlite.support.SQLiteConnectionRepositoryHelper; import org.springframework.social.connect.support.ConnectionFactoryRegistry; import org.springframework.social.connect.support.OAuth1ConnectionFactory; import org.springframework.social.connect.support.OAuth2ConnectionFactory; import org.springframework.social.oauth1.OAuth1Operations; import org.springframework.social.oauth1.OAuth1ServiceProvider; import org.springframework.social.oauth2.AccessGrant; import org.springframework.social.oauth2.GrantType; import org.springframework.social.oauth2.OAuth2Operations; import org.springframework.social.oauth2.OAuth2Parameters; import org.springframework.social.oauth2.OAuth2ServiceProvider; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; /** * @author Roy Clarkson */ public class SQLiteUsersConnectionRepositoryTest extends AndroidTestCase { private ConnectionFactoryRegistry connectionFactoryRegistry; private TestFacebookConnectionFactory connectionFactory; private TextEncryptor textEncryptor; private SQLiteOpenHelper repositoryHelper; private SQLiteUsersConnectionRepository usersConnectionRepository; private ConnectionRepository connectionRepository; @Override protected void setUp() throws Exception { super.setUp(); // creates the database, if it does not exist repositoryHelper = new SQLiteConnectionRepositoryHelper(getContext()); // clear out any existing connections in the database SQLiteDatabase db = repositoryHelper.getWritableDatabase(); db.delete("UserConnection", null, null); db.close(); connectionFactoryRegistry = new ConnectionFactoryRegistry(); connectionFactory = new TestFacebookConnectionFactory(); connectionFactoryRegistry.addConnectionFactory(connectionFactory); // generates a random 8-byte salt that is then hex-encoded String salt = AndroidKeyGenerators.string().generateKey(); String password = "Unit tests are cool!"; textEncryptor = AndroidEncryptors.text(password, salt); usersConnectionRepository = new SQLiteUsersConnectionRepository(repositoryHelper, connectionFactoryRegistry, textEncryptor); connectionRepository = usersConnectionRepository.createConnectionRepository("1"); } @Override public void tearDown() { connectionFactoryRegistry = null; connectionFactory = null; textEncryptor = null; repositoryHelper = null; usersConnectionRepository = null; if (repositoryHelper != null) { repositoryHelper.close(); } } @MediumTest public void testFindUserWithConnection() { insertFacebookConnection(); List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connectionRepository.getPrimaryConnection(TestFacebookApi.class)); assertEquals("1", userIds.get(0)); } @MediumTest public void testFindUserIdWithConnectionNoSuchConnection() { Connection<TestFacebookApi> connection = connectionFactory.createConnection(new AccessGrant("12345")); assertEquals(0, usersConnectionRepository.findUserIdsWithConnection(connection).size()); } @MediumTest public void testFindUserIdWithConnectionMultipleConnectionsToSameProviderUser() { insertFacebookConnection(); insertFacebookConnectionSameFacebookUser(); List<String> localUserIds = usersConnectionRepository.findUserIdsWithConnection(connectionRepository.getPrimaryConnection(TestFacebookApi.class)); assertEquals(2, localUserIds.size()); assertEquals("1", localUserIds.get(0)); assertEquals("2", localUserIds.get(1)); } @MediumTest public void testFindUserIdsConnectedTo() { insertFacebookConnection(); insertFacebookConnection3(); Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo("facebook", new HashSet<String>(Arrays.asList("9", "11"))); assertEquals(2, userIds.size()); assertTrue(userIds.contains("1")); assertTrue(userIds.contains("2")); } @MediumTest @SuppressWarnings("unchecked") public void testFindAllConnections() { connectionFactoryRegistry.addConnectionFactory(new TestTwitterConnectionFactory()); insertTwitterConnection(); insertFacebookConnection(); MultiValueMap<String, Connection<?>> connections = connectionRepository.findAllConnections(); assertEquals(2, connections.size()); Connection<TestFacebookApi> facebook = (Connection<TestFacebookApi>) connections.getFirst("facebook"); assertFacebookConnection(facebook); Connection<TestTwitterApi> twitter = (Connection<TestTwitterApi>) connections.getFirst("twitter"); assertTwitterConnection(twitter); } @MediumTest public void testFindAllConnectionsMultipleConnectionResults() { connectionFactoryRegistry.addConnectionFactory(new TestTwitterConnectionFactory()); insertTwitterConnection(); insertFacebookConnection(); insertFacebookConnection2(); MultiValueMap<String, Connection<?>> connections = connectionRepository.findAllConnections(); assertEquals(2, connections.size()); assertEquals(2, connections.get("facebook").size()); assertEquals(1, connections.get("twitter").size()); } @MediumTest public void testFindAllConnectionsEmptyResult() { connectionFactoryRegistry.addConnectionFactory(new TestTwitterConnectionFactory()); MultiValueMap<String, Connection<?>> connections = connectionRepository.findAllConnections(); assertEquals(2, connections.size()); assertEquals(0, connections.get("facebook").size()); assertEquals(0, connections.get("twitter").size()); } @MediumTest public void testNoSuchConnectionFactory() { boolean success = false; try { insertTwitterConnection(); connectionRepository.findAllConnections(); } catch (IllegalArgumentException e) { success = true; } assertTrue("Expected IllegalArgumentException", success); } @MediumTest @SuppressWarnings("unchecked") public void testFindConnectionsByProviderId() { connectionFactoryRegistry.addConnectionFactory(new TestTwitterConnectionFactory()); insertTwitterConnection(); List<Connection<?>> connections = connectionRepository.findConnections("twitter"); assertEquals(1, connections.size()); assertTwitterConnection((Connection<TestTwitterApi>) connections.get(0)); } @MediumTest public void testFindConnectionsByProviderIdEmptyResult() { assertTrue(connectionRepository.findConnections("facebook").isEmpty()); } @MediumTest public void testFindConnectionsByApi() { insertFacebookConnection(); insertFacebookConnection2(); List<Connection<TestFacebookApi>> connections = connectionRepository.findConnections(TestFacebookApi.class); assertEquals(2, connections.size()); assertFacebookConnection(connections.get(0)); } @MediumTest public void testFindConnectionsByApiEmptyResult() { assertTrue(connectionRepository.findConnections(TestFacebookApi.class).isEmpty()); } @MediumTest @SuppressWarnings("unchecked") public void testFindConnectionsToUsers() { connectionFactoryRegistry.addConnectionFactory(new TestTwitterConnectionFactory()); insertTwitterConnection(); insertFacebookConnection(); insertFacebookConnection2(); MultiValueMap<String, String> providerUsers = new LinkedMultiValueMap<String, String>(); providerUsers.add("facebook", "10"); providerUsers.add("facebook", "9"); providerUsers.add("twitter", "1"); MultiValueMap<String, Connection<?>> connectionsForUsers = connectionRepository.findConnectionsToUsers(providerUsers); assertEquals(2, connectionsForUsers.size()); assertEquals("10", connectionsForUsers.getFirst("facebook").getKey().getProviderUserId()); assertFacebookConnection((Connection<TestFacebookApi>) connectionsForUsers.get("facebook").get(1)); assertTwitterConnection((Connection<TestTwitterApi>) connectionsForUsers.getFirst("twitter")); } @MediumTest public void testFindConnectionsToUsersEmptyResult() { MultiValueMap<String, String> providerUsers = new LinkedMultiValueMap<String, String>(); providerUsers.add("facebook", "1"); assertTrue(connectionRepository.findConnectionsToUsers(providerUsers).isEmpty()); } @MediumTest public void testFindConnectionsToUsersEmptyInput() { boolean success = false; try { MultiValueMap<String, String> providerUsers = new LinkedMultiValueMap<String, String>(); connectionRepository.findConnectionsToUsers(providerUsers); } catch (IllegalArgumentException e) { success = true; } assertTrue("Expected IllegalArgumentException", success); } @MediumTest @SuppressWarnings("unchecked") public void findConnectionByKey() { insertFacebookConnection(); assertFacebookConnection((Connection<TestFacebookApi>) connectionRepository.getConnection(new ConnectionKey("facebook", "9"))); } @MediumTest public void findConnectionByKeyNoSuchConnection() { boolean success = false; try { connectionRepository.getConnection(new ConnectionKey("facebook", "bogus")); } catch (NoSuchConnectionException e) { success = true; } assertTrue("Expected NoSuchConnectionException", success); } @MediumTest public void testFindConnectionsByApiToUser() { insertFacebookConnection(); insertFacebookConnection2(); assertFacebookConnection(connectionRepository.getConnection(TestFacebookApi.class, "9")); assertEquals("10", connectionRepository.getConnection(TestFacebookApi.class, "10").getKey().getProviderUserId()); } @MediumTest public void testFindConnectionByApiToUserNoSuchConnection() { boolean success = false; try { assertFacebookConnection(connectionRepository.getConnection(TestFacebookApi.class, "9")); } catch (NoSuchConnectionException e) { success = true; } assertTrue("Expected NoSuchConnectionException", success); } @MediumTest public void testGetPrimaryConnection() { insertFacebookConnection(); assertFacebookConnection(connectionRepository.getPrimaryConnection(TestFacebookApi.class)); } @MediumTest public void testGetPrimaryConnectionSelectFromMultipleByRank() { insertFacebookConnection2(); insertFacebookConnection(); assertFacebookConnection(connectionRepository.getPrimaryConnection(TestFacebookApi.class)); } public void testGetPrimaryConnectionNotConnected() { boolean success = false; try { connectionRepository.getPrimaryConnection(TestFacebookApi.class); } catch (NotConnectedException e) { success = true; } assertTrue("Expected NotConnectedException", success); } @MediumTest public void testRemoveConnections() { insertFacebookConnection(); insertFacebookConnection2(); assertTrue(queryConnectionExists("facebook")); connectionRepository.removeConnections("facebook"); assertFalse(queryConnectionExists("facebook")); } @MediumTest public void testRemoveConnectionsToProviderNoOp() { connectionRepository.removeConnections("twitter"); } @MediumTest public void testRemoveConnection() { insertFacebookConnection(); assertTrue(queryConnectionExists("facebook")); connectionRepository.removeConnection(new ConnectionKey("facebook", "9")); assertFalse(queryConnectionExists("facebook")); } @MediumTest public void testRemoveConnectionNoOp() { connectionRepository.removeConnection(new ConnectionKey("facebook", "1")); } @MediumTest public void testAddConnection() { Connection<TestFacebookApi> connection = connectionFactory.createConnection(new AccessGrant("123456789", null, "987654321", 3600L)); connectionRepository.addConnection(connection); Connection<TestFacebookApi> restoredConnection = connectionRepository.getPrimaryConnection(TestFacebookApi.class); assertEquals(connection, restoredConnection); assertNewConnection(restoredConnection); } @MediumTest public void testAddConnectionDuplicate() { boolean success = false; try { Connection<TestFacebookApi> connection = connectionFactory.createConnection(new AccessGrant("123456789", null, "987654321", 3600L)); connectionRepository.addConnection(connection); connectionRepository.addConnection(connection); } catch (DuplicateConnectionException e) { success = true; } assertTrue("Expected DuplicateConnectionException", success); } @MediumTest public void testUpdateConnectionProfileFields() { connectionFactoryRegistry.addConnectionFactory(new TestTwitterConnectionFactory()); insertTwitterConnection(); Connection<TestTwitterApi> twitter = connectionRepository.findPrimaryConnection(TestTwitterApi.class); assertEquals("http://twitter.com/kdonald/picture", twitter.getImageUrl()); twitter.sync(); assertEquals("http://twitter.com/kdonald/a_new_picture", twitter.getImageUrl()); connectionRepository.updateConnection(twitter); Connection<TestTwitterApi> twitter2 = connectionRepository.findPrimaryConnection(TestTwitterApi.class); assertEquals("http://twitter.com/kdonald/a_new_picture", twitter2.getImageUrl()); } @MediumTest public void testUpdateConnectionAccessFields() { insertFacebookConnection(); Connection<TestFacebookApi> facebook = connectionRepository.findPrimaryConnection(TestFacebookApi.class); assertEquals("234567890", facebook.getApi().getAccessToken()); facebook.refresh(); connectionRepository.updateConnection(facebook); Connection<TestFacebookApi> facebook2 = connectionRepository.findPrimaryConnection(TestFacebookApi.class); assertEquals("765432109", facebook2.getApi().getAccessToken()); ConnectionData data = facebook.createData(); assertEquals("654321098", data.getRefreshToken()); } // helpers private void insertTwitterConnection() { ContentValues values = new ContentValues(); values.put("userId", "1"); values.put("providerId", "twitter"); values.put("providerUserId", "1"); values.put("rank", 1); values.put("displayName", "@kdonald"); values.put("profileUrl", "http://twitter.com/kdonald"); values.put("imageUrl", "http://twitter.com/kdonald/picture"); values.put("accessToken", encrypt("123456789")); values.put("secret", encrypt("987654321")); values.putNull("refreshToken"); values.putNull("expireTime"); insertConnection(values); } private void insertFacebookConnection() { ContentValues values = new ContentValues(); values.put("userId", "1"); values.put("providerId", "facebook"); values.put("providerUserId", "9"); values.put("rank", 1); values.putNull("displayName"); values.putNull("profileUrl"); values.putNull("imageUrl"); values.put("accessToken", encrypt("234567890")); values.putNull("secret"); values.put("refreshToken", encrypt("345678901")); values.put("expireTime", System.currentTimeMillis() + 3600000); insertConnection(values); } private void insertFacebookConnection2() { ContentValues values = new ContentValues(); values.put("userId", "1"); values.put("providerId", "facebook"); values.put("providerUserId", "10"); values.put("rank", 2); values.putNull("displayName"); values.putNull("profileUrl"); values.putNull("imageUrl"); values.put("accessToken", encrypt("456789012")); values.putNull("secret"); values.put("refreshToken", encrypt("56789012")); values.put("expireTime", System.currentTimeMillis() + 3600000); insertConnection(values); } private void insertFacebookConnection3() { ContentValues values = new ContentValues(); values.put("userId", "2"); values.put("providerId", "facebook"); values.put("providerUserId", "11"); values.put("rank", 2); values.putNull("displayName"); values.putNull("profileUrl"); values.putNull("imageUrl"); values.put("accessToken", encrypt("456789012")); values.putNull("secret"); values.put("refreshToken", encrypt("56789012")); values.put("expireTime", System.currentTimeMillis() + 3600000); insertConnection(values); } private void insertFacebookConnectionSameFacebookUser() { ContentValues values = new ContentValues(); values.put("userId", "2"); values.put("providerId", "facebook"); values.put("providerUserId", "9"); values.put("rank", 1); values.putNull("displayName"); values.putNull("profileUrl"); values.putNull("imageUrl"); values.put("accessToken", encrypt("234567890")); values.putNull("secret"); values.put("refreshToken", encrypt("345678901")); values.put("expireTime", System.currentTimeMillis() + 3600000); insertConnection(values); } private String encrypt(String text) { return text != null ? textEncryptor.encrypt(text) : text; } private void insertConnection(final ContentValues values) { SQLiteDatabase db = repositoryHelper.getWritableDatabase(); db.insertOrThrow("UserConnection", null, values); db.close(); } private boolean queryConnectionExists(String providerId) { final String sql = "select exists (select 1 from UserConnection where providerId = ?)"; final String[] selectionArgs = { providerId }; SQLiteDatabase db = repositoryHelper.getReadableDatabase(); Cursor c = db.rawQuery(sql, selectionArgs); c.moveToFirst(); boolean b = (c.getInt(0) != 0); c.close(); db.close(); return b; } private void assertNewConnection(Connection<TestFacebookApi> connection) { assertEquals("facebook", connection.getKey().getProviderId()); assertEquals("9", connection.getKey().getProviderUserId()); assertEquals("Keith Donald", connection.getDisplayName()); assertEquals("http://facebook.com/keith.donald", connection.getProfileUrl()); assertEquals("http://facebook.com/keith.donald/picture", connection.getImageUrl()); assertTrue(connection.test()); TestFacebookApi api = connection.getApi(); assertNotNull(api); assertEquals("123456789", api.getAccessToken()); assertEquals("123456789", connection.createData().getAccessToken()); assertEquals("987654321", connection.createData().getRefreshToken()); } private void assertTwitterConnection(Connection<TestTwitterApi> twitter) { assertEquals(new ConnectionKey("twitter", "1"), twitter.getKey()); assertEquals("@kdonald", twitter.getDisplayName()); assertEquals("http://twitter.com/kdonald", twitter.getProfileUrl()); assertEquals("http://twitter.com/kdonald/picture", twitter.getImageUrl()); TestTwitterApi twitterApi = twitter.getApi(); assertEquals("123456789", twitterApi.getAccessToken()); assertEquals("987654321", twitterApi.getSecret()); twitter.sync(); assertEquals("http://twitter.com/kdonald/a_new_picture", twitter.getImageUrl()); } private void assertFacebookConnection(Connection<TestFacebookApi> facebook) { assertEquals(new ConnectionKey("facebook", "9"), facebook.getKey()); assertEquals(null, facebook.getDisplayName()); assertEquals(null, facebook.getProfileUrl()); assertEquals(null, facebook.getImageUrl()); TestFacebookApi facebookApi = facebook.getApi(); assertEquals("234567890", facebookApi.getAccessToken()); facebook.sync(); assertEquals("Keith Donald", facebook.getDisplayName()); assertEquals("http://facebook.com/keith.donald", facebook.getProfileUrl()); assertEquals("http://facebook.com/keith.donald/picture", facebook.getImageUrl()); } // test facebook provider private static class TestFacebookConnectionFactory extends OAuth2ConnectionFactory<TestFacebookApi> { public TestFacebookConnectionFactory() { super("facebook", new TestFacebookServiceProvider(), new TestFacebookApiAdapter()); } } private static class TestFacebookServiceProvider implements OAuth2ServiceProvider<TestFacebookApi> { public OAuth2Operations getOAuthOperations() { return new OAuth2Operations() { public String buildAuthorizeUrl(GrantType grantType, OAuth2Parameters params) { return null; } public String buildAuthenticateUrl(GrantType grantType, OAuth2Parameters params) { return null; } public String buildAuthorizeUrl(OAuth2Parameters params) { return null; } public String buildAuthenticateUrl(OAuth2Parameters params) { return null; } public AccessGrant exchangeForAccess(String authorizationGrant, String redirectUri, MultiValueMap<String, String> additionalParameters) { return null; } public AccessGrant exchangeCredentialsForAccess(String username, String password, MultiValueMap<String, String> additionalParameters) { return null; } public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) { return new AccessGrant("765432109", "read", "654321098", 3600L); } public AccessGrant refreshAccess(String refreshToken, String scope, MultiValueMap<String, String> additionalParameters) { return new AccessGrant("765432109", "read", "654321098", 3600L); } public AccessGrant authenticateClient() { return null; } public AccessGrant authenticateClient(String scope) { return null; } }; } public TestFacebookApi getApi(final String accessToken) { return new TestFacebookApi() { public String getAccessToken() { return accessToken; } }; } } public interface TestFacebookApi { String getAccessToken(); } private static class TestFacebookApiAdapter implements ApiAdapter<TestFacebookApi> { private String accountId = "9"; private String name = "Keith Donald"; private String profileUrl = "http://facebook.com/keith.donald"; private String profilePictureUrl = "http://facebook.com/keith.donald/picture"; public boolean test(TestFacebookApi api) { return true; } public void setConnectionValues(TestFacebookApi api, ConnectionValues values) { values.setProviderUserId(accountId); values.setDisplayName(name); values.setProfileUrl(profileUrl); values.setImageUrl(profilePictureUrl); } public UserProfile fetchUserProfile(TestFacebookApi api) { return new UserProfileBuilder().setName(name).setEmail("keith@interface21.com").setUsername("Keith.Donald").build(); } public void updateStatus(TestFacebookApi api, String message) { } } // test twitter provider private static class TestTwitterConnectionFactory extends OAuth1ConnectionFactory<TestTwitterApi> { public TestTwitterConnectionFactory() { super("twitter", new TestTwitterServiceProvider(), new TestTwitterApiAdapter()); } } private static class TestTwitterServiceProvider implements OAuth1ServiceProvider<TestTwitterApi> { public OAuth1Operations getOAuthOperations() { return null; } public TestTwitterApi getApi(final String accessToken, final String secret) { return new TestTwitterApi() { public String getAccessToken() { return accessToken; } public String getSecret() { return secret; } }; } } public interface TestTwitterApi { String getAccessToken(); String getSecret(); } private static class TestTwitterApiAdapter implements ApiAdapter<TestTwitterApi> { private String accountId = "1"; private String name = "@kdonald"; private String profileUrl = "http://twitter.com/kdonald"; private String profilePictureUrl = "http://twitter.com/kdonald/a_new_picture"; public boolean test(TestTwitterApi api) { return true; } public void setConnectionValues(TestTwitterApi api, ConnectionValues values) { values.setProviderUserId(accountId); values.setDisplayName(name); values.setProfileUrl(profileUrl); values.setImageUrl(profilePictureUrl); } public UserProfile fetchUserProfile(TestTwitterApi api) { return new UserProfileBuilder().setName(name).setUsername("kdonald").build(); } public void updateStatus(TestTwitterApi api, String message) { } } }