diff --git a/Parse/src/main/java/com/parse/AnonymousAuthenticationProvider.java b/Parse/src/main/java/com/parse/AnonymousAuthenticationProvider.java index 0260e9497..c15b48eef 100644 --- a/Parse/src/main/java/com/parse/AnonymousAuthenticationProvider.java +++ b/Parse/src/main/java/com/parse/AnonymousAuthenticationProvider.java @@ -18,7 +18,7 @@ * An authentication provider that generates a random UUID that will be used as * a key to identify this anonymous user until the user has been claimed. */ -/** package */ class AnonymousAuthenticationProvider extends ParseAuthenticationProvider { +/** package */ class AnonymousAuthenticationProvider implements ParseAuthenticationProvider { @Override public Task> authenticateAsync() { @@ -32,8 +32,9 @@ public Map getAuthData() { } @Override - public void deauthenticate() { + public Task deauthenticateAsync() { // do nothing + return Task.forResult(null); } @Override diff --git a/Parse/src/main/java/com/parse/CachedCurrentUserController.java b/Parse/src/main/java/com/parse/CachedCurrentUserController.java index 16adc49d8..331e359fd 100644 --- a/Parse/src/main/java/com/parse/CachedCurrentUserController.java +++ b/Parse/src/main/java/com/parse/CachedCurrentUserController.java @@ -46,7 +46,7 @@ public Task setAsync(final ParseUser user) { public Task then(Task toAwait) throws Exception { return toAwait.continueWithTask(new Continuation>() { @Override - public Task then(Task ignored) throws Exception { + public Task then(Task task) throws Exception { ParseUser oldCurrentUser; synchronized (mutex) { oldCurrentUser = currentUser; @@ -55,9 +55,13 @@ public Task then(Task ignored) throws Exception { if (oldCurrentUser != null && oldCurrentUser != user) { // We don't need to revoke the token since we're not explicitly calling logOut // We don't need to remove persisted files since we're overwriting them - oldCurrentUser.logOutInternal(); + return oldCurrentUser.logOutAsync(false); } - + return task; + } + }).continueWithTask(new Continuation>() { + @Override + public Task then(Task task) throws Exception { synchronized (user.mutex) { user.setIsCurrentUser(true); user.synchronizeAllAuthData(); diff --git a/Parse/src/main/java/com/parse/ParseAnonymousUtils.java b/Parse/src/main/java/com/parse/ParseAnonymousUtils.java index ee3e2c5c1..b84f06378 100644 --- a/Parse/src/main/java/com/parse/ParseAnonymousUtils.java +++ b/Parse/src/main/java/com/parse/ParseAnonymousUtils.java @@ -8,6 +8,9 @@ */ package com.parse; +import java.util.Map; + +import bolts.Continuation; import bolts.Task; /** @@ -62,7 +65,13 @@ public static boolean isLinked(ParseUser user) { * @return A Task that will be resolved when logging in is completed. */ public static Task logInInBackground() { - return getProvider().logInAsync(); + final ParseAuthenticationProvider provider = getProvider(); + return provider.authenticateAsync().onSuccessTask(new Continuation, Task>() { + @Override + public Task then(Task> task) throws Exception { + return ParseUser.logInWithAsync(provider.getAuthType(), task.getResult()); + } + }); } /** diff --git a/Parse/src/main/java/com/parse/ParseAuthenticationManager.java b/Parse/src/main/java/com/parse/ParseAuthenticationManager.java new file mode 100644 index 000000000..45e602e70 --- /dev/null +++ b/Parse/src/main/java/com/parse/ParseAuthenticationManager.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import java.util.HashMap; +import java.util.Map; + +import bolts.Continuation; +import bolts.Task; + +/*** package */ class ParseAuthenticationManager { + + private final Object lock = new Object(); + private final Map authenticationProviders = new HashMap<>(); + private final ParseCurrentUserController controller; + + public ParseAuthenticationManager(ParseCurrentUserController controller) { + this.controller = controller; + } + + public void register(ParseAuthenticationProvider provider) { + final String authType = provider.getAuthType(); + if (authType == null) { + throw new IllegalArgumentException("Invalid authType: " + null); + } + + synchronized (lock) { + if (authenticationProviders.containsKey(authType)) { + throw new IllegalStateException("Another " + authType + " provider was already registered: " + + authenticationProviders.get(authType)); + } + authenticationProviders.put(provider.getAuthType(), provider); + } + + if (provider instanceof AnonymousAuthenticationProvider) { + // There's nothing to synchronize + return; + } + + // Synchronize the current user with the auth provider. + controller.getAsync(false).onSuccess(new Continuation() { + @Override + public Void then(Task task) throws Exception { + ParseUser user = task.getResult(); + if (user != null) { + user.synchronizeAuthData(authType); + } + return null; + } + }); + } + + public boolean restoreAuthentication(String authType, Map authData) { + ParseAuthenticationProvider provider; + synchronized (lock) { + provider = authenticationProviders.get(authType); + } + return provider == null || provider.restoreAuthentication(authData); + } + + public Task deauthenticateAsync(String authType) { + ParseAuthenticationProvider provider; + synchronized (lock) { + provider = authenticationProviders.get(authType); + } + if (provider != null) { + return provider.deauthenticateAsync(); + } + return Task.forResult(null); + } +} diff --git a/Parse/src/main/java/com/parse/ParseAuthenticationProvider.java b/Parse/src/main/java/com/parse/ParseAuthenticationProvider.java index bb953be10..2845ca1f0 100644 --- a/Parse/src/main/java/com/parse/ParseAuthenticationProvider.java +++ b/Parse/src/main/java/com/parse/ParseAuthenticationProvider.java @@ -10,19 +10,18 @@ import java.util.Map; -import bolts.Continuation; import bolts.Task; /** * Provides a general interface for delegation of the authentication process. */ -/** package */ abstract class ParseAuthenticationProvider { +/** package */ interface ParseAuthenticationProvider { /** * Provides a unique name for the type of authentication the provider does. * For example, the FacebookAuthenticationProvider would return "facebook". */ - public abstract String getAuthType(); + String getAuthType(); /** * Begins the authentication process and invokes onSuccess() or onError() on @@ -30,13 +29,13 @@ * * @return A task that will be resolved upon the completion of authentication. */ - public abstract Task> authenticateAsync(); + Task> authenticateAsync(); /** * Deauthenticates (logs out) the user associated with this provider. This * call may block. */ - public abstract void deauthenticate(); + Task deauthenticateAsync(); /** * Restores authentication that has been serialized, such as session keys, @@ -49,35 +48,5 @@ * value indicates that the user should no longer be associated * because of bad auth data. */ - public abstract boolean restoreAuthentication(Map authData); - - public Task logInAsync() { - return authenticateAsync().onSuccessTask(new Continuation, Task>() { - @Override - public Task then(Task> task) throws Exception { - return logInAsync(task.getResult()); - } - }); - } - - public Task logInAsync(Map authData) { - return ParseUser.logInWithAsync(getAuthType(), authData); - } - - public Task linkAsync(final ParseUser user) { - return authenticateAsync().onSuccessTask(new Continuation, Task>() { - @Override - public Task then(Task> task) throws Exception { - return linkAsync(user, task.getResult()); - } - }); - } - - public Task linkAsync(ParseUser user, Map authData) { - return user.linkWithAsync(getAuthType(), authData, user.getSessionToken()); - } - - public Task unlinkAsync(ParseUser user) { - return user.unlinkFromAsync(getAuthType()); - } + boolean restoreAuthentication(Map authData); } diff --git a/Parse/src/main/java/com/parse/ParseCorePlugins.java b/Parse/src/main/java/com/parse/ParseCorePlugins.java index 6f24046af..366e8cb2e 100644 --- a/Parse/src/main/java/com/parse/ParseCorePlugins.java +++ b/Parse/src/main/java/com/parse/ParseCorePlugins.java @@ -35,6 +35,9 @@ public static ParseCorePlugins getInstance() { private AtomicReference currentInstallationController = new AtomicReference<>(); + private AtomicReference authenticationController = + new AtomicReference<>(); + private AtomicReference queryController = new AtomicReference<>(); private AtomicReference fileController = new AtomicReference<>(); private AtomicReference analyticsController = new AtomicReference<>(); @@ -59,6 +62,8 @@ private ParseCorePlugins() { currentUserController.set(null); currentInstallationController.set(null); + authenticationController.set(null); + queryController.set(null); fileController.set(null); analyticsController.set(null); @@ -285,6 +290,23 @@ public void registerCurrentInstallationController(ParseCurrentInstallationContro } } + public ParseAuthenticationManager getAuthenticationManager() { + if (authenticationController.get() == null) { + ParseAuthenticationManager controller = + new ParseAuthenticationManager(getCurrentUserController()); + authenticationController.compareAndSet(null, controller); + } + return authenticationController.get(); + } + + public void registerAuthenticationManager(ParseAuthenticationManager manager) { + if (!authenticationController.compareAndSet(null, manager)) { + throw new IllegalStateException( + "Another authentication manager was already registered: " + + authenticationController.get()); + } + } + public ParseDefaultACLController getDefaultACLController() { if (defaultACLController.get() == null) { ParseDefaultACLController controller = new ParseDefaultACLController(); diff --git a/Parse/src/main/java/com/parse/ParseUser.java b/Parse/src/main/java/com/parse/ParseUser.java index da65bf99e..d80c3373e 100644 --- a/Parse/src/main/java/com/parse/ParseUser.java +++ b/Parse/src/main/java/com/parse/ParseUser.java @@ -10,6 +10,7 @@ import org.json.JSONObject; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -37,8 +38,6 @@ public class ParseUser extends ParseObject { private static final List READ_ONLY_KEYS = Collections.unmodifiableList( Arrays.asList(KEY_SESSION_TOKEN, KEY_AUTH_DATA)); - private static Map authenticationProviders = new HashMap<>(); - /** * Constructs a query for {@code ParseUser}. * @@ -56,6 +55,10 @@ public static ParseQuery getQuery() { return ParseCorePlugins.getInstance().getCurrentUserController(); } + /* package for tests */ static ParseAuthenticationManager getAuthenticationManager() { + return ParseCorePlugins.getInstance().getAuthenticationManager(); + } + /** package */ static class State extends ParseObject.State { /** package */ static class Builder extends Init { @@ -245,6 +248,7 @@ public void remove(String key) { } /* package for tests */ void cleanUpAuthData() { + ParseAuthenticationManager controller = getAuthenticationManager(); synchronized (mutex) { Map> authData = getState().authData(); if (authData.size() == 0) { @@ -256,9 +260,7 @@ public void remove(String key) { Map.Entry> entry = i.next(); if (entry.getValue() == null) { i.remove(); - if (authenticationProviders.containsKey(entry.getKey())) { - authenticationProviders.get(entry.getKey()).restoreAuthentication(null); - } + controller.restoreAuthentication(entry.getKey(), null); } } @@ -971,17 +973,19 @@ public static void logOut() { //TODO (grantland): Add to taskQueue /* package */ Task logOutAsync() { - String oldSessionToken = logOutInternal(); - return ParseSession.revokeAsync(oldSessionToken); + return logOutAsync(true); } - /* package */ String logOutInternal() { - String oldSessionToken; + /* package */ Task logOutAsync(boolean revoke) { + ParseAuthenticationManager controller = getAuthenticationManager(); + List> tasks = new ArrayList<>(); + final String oldSessionToken; + synchronized (mutex) { oldSessionToken = getState().sessionToken(); for (Map.Entry> entry : getAuthData().entrySet()) { - logOutWith(entry.getKey()); + tasks.add(controller.deauthenticateAsync(entry.getKey())); } State newState = getState().newBuilder() @@ -991,7 +995,12 @@ public static void logOut() { isCurrentUser = false; setState(newState); } - return oldSessionToken; + + if (revoke) { + tasks.add(ParseSession.revokeAsync(oldSessionToken)); + } + + return Task.whenAll(tasks); } /** @@ -1059,23 +1068,13 @@ public ParseUser fetchIfNeeded() throws ParseException { return authData.containsKey(authType) && authData.get(authType) != null; } - private void synchronizeAuthData(String authType) { + /* package */ void synchronizeAuthData(String authType) { synchronized (mutex) { - if (!this.isCurrentUser()) { - return; - } - ParseAuthenticationProvider provider = authenticationProviders.get(authType); - if (provider == null) { + if (!isCurrentUser()) { return; } - synchronizeAuthData(provider); - } - } - - /* package */ void synchronizeAuthData(ParseAuthenticationProvider provider) { - synchronized (mutex) { - String authType = provider.getAuthType(); - boolean success = provider.restoreAuthentication(getAuthData(authType)); + boolean success = getAuthenticationManager() + .restoreAuthentication(authType, getAuthData(authType)); if (!success) { unlinkFromAsync(authType); } @@ -1115,23 +1114,15 @@ public Task then(Task task) throws Exception { } /* package */ static void registerAuthenticationProvider(ParseAuthenticationProvider provider) { - authenticationProviders.put(provider.getAuthType(), provider); - - if (provider instanceof AnonymousAuthenticationProvider) { - // There's nothing to synchronize - return; - } - - // Synchronize the current user with the auth provider. - //TODO (grantland): Possible disk I/O on main thread - ParseUser user = getCurrentUser(); - if (user != null) { - user.synchronizeAuthData(provider); - } + getAuthenticationManager().register(provider); } /* package */ static Task logInWithAsync( final String authType, final Map authData) { + if (authType == null) { + throw new IllegalArgumentException("Invalid authType: " + null); + } + final Continuation> logInWithTask = new Continuation>() { @Override public Task then(Task task) throws Exception { @@ -1198,9 +1189,7 @@ public Task then(Task task) throws Exception { // Try to link the current user with third party user, unless a user is already linked // to that third party user, then we'll just create a new user and link it with the // third party user. New users will not be linked to the previous user's data. - return user.linkWithAsync( - authType, authData, user.getSessionToken() - ).continueWithTask(new Continuation>() { + return user.linkWithAsync(authType, authData).continueWithTask(new Continuation>() { @Override public Task then(Task task) throws Exception { if (task.isFaulted()) { @@ -1255,8 +1244,10 @@ public Task then(Task task) throws Exception { } } - /* package */ Task linkWithAsync( - final String authType, final Map authData, final String sessionToken) { + private Task linkWithAsync( + final String authType, + final Map authData, + final String sessionToken) { return taskQueue.enqueue(new Continuation>() { @Override public Task then(Task task) throws Exception { @@ -1265,13 +1256,12 @@ public Task then(Task task) throws Exception { }); } - private void logOutWith(String authType) { - synchronized (mutex) { - ParseAuthenticationProvider provider = authenticationProviders.get(authType); - if (provider != null && isLinked(authType)) { - provider.deauthenticate(); - } + /* package */ Task linkWithAsync( + String authType, Map authData) { + if (authType == null) { + throw new IllegalArgumentException("Invalid authType: " + null); } + return linkWithAsync(authType, authData, getSessionToken()); } //endregion diff --git a/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java b/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java index fc23b11f1..b83211cc0 100644 --- a/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java +++ b/Parse/src/test/java/com/parse/CachedCurrentUserControllerTest.java @@ -10,11 +10,8 @@ import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import java.io.File; import java.util.HashMap; import java.util.Map; @@ -25,6 +22,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -52,6 +50,7 @@ public void tearDown() { public void testSetAsyncWithOldInMemoryCurrentUser() throws Exception { // Mock currentUser in memory ParseUser oldCurrentUser = mock(ParseUser.class); + when(oldCurrentUser.logOutAsync(anyBoolean())).thenReturn(Task.forResult(null)); ParseUser.State state = new ParseUser.State.Builder() .put("key", "value") @@ -68,7 +67,7 @@ public void testSetAsyncWithOldInMemoryCurrentUser() throws Exception { ParseTaskUtils.wait(controller.setAsync(currentUser)); // Make sure oldCurrentUser logout - verify(oldCurrentUser, times(1)).logOutInternal(); + verify(oldCurrentUser, times(1)).logOutAsync(false); // Verify it was persisted verify(store, times(1)).setAsync(currentUser); // TODO(mengyan): Find a way to verify user.synchronizeAllAuthData() is called @@ -108,6 +107,7 @@ public void testSetAsyncWithNoInMemoryCurrentUser() throws Exception { public void testSetAsyncWithPersistFailure() throws Exception { // Mock currentUser in memory ParseUser oldCurrentUser = mock(ParseUser.class); + when(oldCurrentUser.logOutAsync(anyBoolean())).thenReturn(Task.forResult(null)); ParseUser currentUser = new ParseUser(); ParseObjectStore store = @@ -122,7 +122,7 @@ public void testSetAsyncWithPersistFailure() throws Exception { ParseTaskUtils.wait(controller.setAsync(currentUser)); // Make sure oldCurrentUser logout - verify(oldCurrentUser, times(1)).logOutInternal(); + verify(oldCurrentUser, times(1)).logOutAsync(false); // Verify we tried to persist verify(store, times(1)).setAsync(currentUser); // TODO(mengyan): Find a way to verify user.synchronizeAllAuthData() is called @@ -159,6 +159,7 @@ public void testGetAsyncWithNoInMemoryCurrentUserAndLazyLogin() throws Exception CachedCurrentUserController controller = new CachedCurrentUserController(store); + ParseCorePlugins.getInstance().registerCurrentUserController(controller); // CurrentUser is null but currentUserMatchesDisk is true happens when a user logout controller.currentUserMatchesDisk = true; diff --git a/Parse/src/test/java/com/parse/ParseAuthenticationManagerTest.java b/Parse/src/test/java/com/parse/ParseAuthenticationManagerTest.java new file mode 100644 index 000000000..cb08014d9 --- /dev/null +++ b/Parse/src/test/java/com/parse/ParseAuthenticationManagerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2015-present, Parse, LLC. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.parse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.HashMap; +import java.util.Map; + +import bolts.Task; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class ParseAuthenticationManagerTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ParseAuthenticationManager manager; + private ParseCurrentUserController controller; + private ParseAuthenticationProvider provider; + + @Before + public void setUp() { + controller = mock(ParseCurrentUserController.class); + manager = new ParseAuthenticationManager(controller); + provider = mock(ParseAuthenticationProvider.class); + when(provider.getAuthType()).thenReturn("test_provider"); + } + + //region testRegister + + @Test + public void testRegisterMultipleShouldThrow() { + when(controller.getAsync(false)).thenReturn(Task.forResult(null)); + ParseAuthenticationProvider provider2 = mock(ParseAuthenticationProvider.class); + when(provider2.getAuthType()).thenReturn("test_provider"); + + manager.register(provider); + + thrown.expect(IllegalStateException.class); + manager.register(provider2); + } + + @Test + public void testRegisterAnonymous() { + ParseAuthenticationProvider anonymous = mock(AnonymousAuthenticationProvider.class); + when(anonymous.getAuthType()).thenReturn("anonymous"); + + manager.register(anonymous); + verifyNoMoreInteractions(controller); + } + + @Test + public void testRegister() { + ParseUser user = mock(ParseUser.class); + when(controller.getAsync(false)).thenReturn(Task.forResult(user)); + + manager.register(provider); + verify(controller).getAsync(false); + verify(user).synchronizeAuthData("test_provider"); + } + + //endregion + + @Test + public void testRestoreAuthentication() { + when(controller.getAsync(false)).thenReturn(Task.forResult(null)); + manager.register(provider); + + Map authData = new HashMap<>(); + manager.restoreAuthentication("test_provider", authData); + + verify(provider).restoreAuthentication(authData); + } + + @Test + public void testDeauthenticateAsync() throws ParseException { + when(controller.getAsync(false)).thenReturn(Task.forResult(null)); + when(provider.deauthenticateAsync()).thenReturn(Task.forResult(null)); + manager.register(provider); + + ParseTaskUtils.wait(manager.deauthenticateAsync("test_provider")); + + verify(provider).deauthenticateAsync(); + } +} diff --git a/Parse/src/test/java/com/parse/ParseUserTest.java b/Parse/src/test/java/com/parse/ParseUserTest.java index 2b03db47d..762a1da06 100644 --- a/Parse/src/test/java/com/parse/ParseUserTest.java +++ b/Parse/src/test/java/com/parse/ParseUserTest.java @@ -448,16 +448,19 @@ public void testLoginWithAsyncWithLinkedLazyUseAndResolveLazinessFailure() throw @Test public void testLoginWithAsyncWithLinkedNotLazyUser() throws Exception { // Register a mock currentUserController to make getCurrentUser work - ParseUser currentUser = new ParseUser(); - currentUser.setObjectId("objectId"); // Make it not lazy. - currentUser.putAuthData(ParseAnonymousUtils.AUTH_TYPE, new HashMap()); - ParseUser partialMockCurrentUser = spy(currentUser); - when(partialMockCurrentUser.getSessionToken()).thenReturn("sessionToken"); + ParseUser.State state = new ParseUser.State.Builder() + .objectId("objectId") // Make it not lazy + .putAuthData(ParseAnonymousUtils.AUTH_TYPE, new HashMap()) + .build(); + ParseUser currentUser = ParseUser.from(state); + ParseUser partialMockCurrentUser = spy(currentUser); // ParseUser.mutex doReturn(Task.forResult(null)) .when(partialMockCurrentUser) - .linkWithAsync(anyString(), Matchers.>any(), anyString()); + .linkWithAsync(anyString(), Matchers.>any()); ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); when(currentUserController.getAsync()).thenReturn(Task.forResult(partialMockCurrentUser)); + when(currentUserController.getAsync(anyBoolean())) + .thenReturn(Task.forResult(partialMockCurrentUser)); ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); String authType = "facebook"; @@ -467,8 +470,7 @@ public void testLoginWithAsyncWithLinkedNotLazyUser() throws Exception { ParseUser userAfterLogin = ParseTaskUtils.wait(ParseUser.logInWithAsync(authType, authData)); // Make sure we link authData - verify(partialMockCurrentUser, times(1)).linkWithAsync( - eq("facebook"), eq(authData), eq("sessionToken")); + verify(partialMockCurrentUser, times(1)).linkWithAsync(authType, authData); assertSame(partialMockCurrentUser, userAfterLogin); } @@ -493,7 +495,7 @@ public void testLoginWithAsyncWithLinkedNotLazyUserLinkFailure() throws Exceptio new ParseException(ParseException.ACCOUNT_ALREADY_LINKED, "Account already linked"); doReturn(Task.forError(linkException)) .when(partialMockCurrentUser) - .linkWithAsync(anyString(), Matchers.>any(), anyString()); + .linkWithAsync(anyString(), Matchers.>any()); ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); when(currentUserController.getAsync()).thenReturn(Task.forResult(partialMockCurrentUser)); when(currentUserController.setAsync(any(ParseUser.class))) @@ -507,8 +509,7 @@ public void testLoginWithAsyncWithLinkedNotLazyUserLinkFailure() throws Exceptio ParseUser userAfterLogin = ParseTaskUtils.wait(ParseUser.logInWithAsync(authType, authData)); // Make sure we link authData - verify(partialMockCurrentUser, times(1)).linkWithAsync( - eq("facebook"), eq(authData), eq("sessionToken")); + verify(partialMockCurrentUser, times(1)).linkWithAsync(authType, authData); // Make sure we login authData verify(userController, times(1)).logInAsync("facebook", authData); // Make sure we save the new created user as currentUser @@ -579,11 +580,13 @@ public void testLinkWithAsyncWithSaveAsyncSuccess() throws Exception { doReturn(Task.forResult(null)) .when(partialMockUser) .saveAsync(anyString(), eq(false), Matchers.>any()); - String authType = "facebook"; + doReturn("sessionTokenAgain") + .when(partialMockUser) + .getSessionToken(); Map authData = new HashMap<>(); authData.put("token", "test"); - ParseTaskUtils.wait(partialMockUser.linkWithAsync(authType, authData, "sessionTokenAgain")); + ParseTaskUtils.wait(partialMockUser.linkWithAsync(provider.getAuthType(), authData)); // Make sure we stripAnonymity assertNull(partialMockUser.getAuthData().get(ParseAnonymousUtils.AUTH_TYPE)); @@ -615,16 +618,19 @@ public void testLinkWithAsyncWithSaveAsyncFailure() throws Exception { doReturn(Task.forError(saveException)) .when(partialMockUser) .saveAsync(anyString(), eq(false), Matchers.>any()); - String facebookAuthType = "facebook"; - Map facebookAuthData = new HashMap<>(); - facebookAuthData.put("facebookToken", "facebookTest"); + doReturn("sessionTokenAgain") + .when(partialMockUser) + .getSessionToken(); + String authType = "facebook"; + Map authData = new HashMap<>(); + authData.put("facebookToken", "facebookTest"); Task linkTask = - partialMockUser.linkWithAsync(facebookAuthType, facebookAuthData, "sessionTokenAgain"); + partialMockUser.linkWithAsync(authType, authData); linkTask.waitForCompletion(); // Make sure new authData is added - assertSame(facebookAuthData, partialMockUser.getAuthData().get("facebook")); + assertSame(authData, partialMockUser.getAuthData().get(authType)); // Make sure we save the user verify(partialMockUser, times(1)) .saveAsync(eq("sessionTokenAgain"), eq(false), Matchers.>any()); @@ -885,49 +891,15 @@ public void testSaveAsyncWithLazyAndNotCurrentUser() throws Exception { //region testLogOutAsync - @Test - public void testLogoutInternal() throws Exception { - // Register a mock currentUserController to make setCurrentUser work - ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); - when(currentUserController.setAsync(any(ParseUser.class))) - .thenReturn(Task.forResult(null)); - when(currentUserController.getAsync(anyBoolean())).thenReturn(Task.forResult(null)); - ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); - // Register a mock authenticationProvider - ParseAuthenticationProvider provider = mock(ParseAuthenticationProvider.class); - when(provider.getAuthType()).thenReturn("facebook"); - when(provider.restoreAuthentication(Matchers.>any())).thenReturn(true); - ParseUser.registerAuthenticationProvider(provider); - - // Set user initial state - String facebookAuthType = "facebook"; - Map facebookAuthData = new HashMap<>(); - facebookAuthData.put("facebookToken", "facebookTest"); - ParseUser.State userState = new ParseUser.State.Builder() - .objectId("test") - .putAuthData(facebookAuthType, facebookAuthData) - .sessionToken("oldSessionToken") - .build(); - ParseUser user = ParseObject.from(userState); - - String oldSessionToken = user.logOutInternal(); - - // Make sure user state is correct - assertFalse(user.isCurrentUser()); - assertFalse(user.isNew()); - assertNull(user.getSessionToken()); - // Make sure provider is deauthenticate - verify(provider, times(1)).deauthenticate(); - // Make sure return sessionToken is correct - assertSame("oldSessionToken", oldSessionToken); - } - @Test public void testLogOutAsync() throws Exception { // Register a mock sessionController to verify revokeAsync() NetworkSessionController sessionController = mock(NetworkSessionController.class); when(sessionController.revokeAsync(anyString())).thenReturn(Task.forResult(null)); ParseCorePlugins.getInstance().registerSessionController(sessionController); + ParseAuthenticationManager manager = mock(ParseAuthenticationManager.class); + when(manager.deauthenticateAsync(anyString())).thenReturn(Task.forResult(null)); + ParseCorePlugins.getInstance().registerAuthenticationManager(manager); // Set user initial state String facebookAuthType = "facebook"; @@ -942,6 +914,7 @@ public void testLogOutAsync() throws Exception { ParseTaskUtils.wait(user.logOutAsync()); + verify(manager).deauthenticateAsync("facebook"); // Verify we revoke session verify(sessionController, times(1)).revokeAsync("r:oldSessionToken"); } @@ -1062,12 +1035,12 @@ public void testUnlinkFromAsyncWithAuthType() throws Exception { ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); // Set user initial state - String facebookAuthType = "facebook"; - Map facebookAuthData = new HashMap<>(); - facebookAuthData.put("facebookToken", "facebookTest"); + String authType = "facebook"; + Map authData = new HashMap<>(); + authData.put("facebookToken", "facebookTest"); ParseUser.State userState = new ParseUser.State.Builder() .objectId("test") - .putAuthData(facebookAuthType, facebookAuthData) + .putAuthData(authType, authData) .build(); ParseUser user = ParseObject.from(userState); ParseUser partialMockUser = spy(user); @@ -1075,7 +1048,7 @@ public void testUnlinkFromAsyncWithAuthType() throws Exception { .when(partialMockUser) .saveAsync(anyString(), Matchers.>any()); - ParseTaskUtils.wait(partialMockUser.unlinkFromAsync("facebook")); + ParseTaskUtils.wait(partialMockUser.unlinkFromAsync(authType)); // Verify we delete authData assertNull(user.getAuthData().get("facebook")); @@ -1083,29 +1056,6 @@ public void testUnlinkFromAsyncWithAuthType() throws Exception { verify(partialMockUser, times(1)).saveAsync(eq("sessionToken"), Matchers.>any()); } - @Test - public void testUnlinkFromAsyncWithNoAuthType() throws Exception { - // Register a mock currentUserController to make getAsync work - ParseCurrentUserController currentUserController = mock(ParseCurrentUserController.class); - when(currentUserController.getAsync()).thenReturn(Task.forResult(null)); - ParseCorePlugins.getInstance().registerCurrentUserController(currentUserController); - - // Set user initial state - String facebookAuthType = "facebook"; - Map facebookAuthData = new HashMap<>(); - facebookAuthData.put("facebookToken", "facebookTest"); - ParseUser.State userState = new ParseUser.State.Builder() - .objectId("test") - .putAuthData(facebookAuthType, facebookAuthData) - .build(); - ParseUser user = ParseObject.from(userState); - - ParseTaskUtils.wait(user.unlinkFromAsync(null)); - - // Verify we do not delete authData - assertEquals(facebookAuthData, user.getAuthData().get("facebook")); - } - //endregion //region testLogin @@ -1341,18 +1291,19 @@ public void testSynchronizeAuthData() throws Exception { ParseUser.registerAuthenticationProvider(provider); // Set user initial state - String facebookAuthType = "facebook"; - Map facebookAuthData = new HashMap<>(); - facebookAuthData.put("facebookToken", "facebookTest"); + String authType = "facebook"; + Map authData = new HashMap<>(); + authData.put("facebookToken", "facebookTest"); ParseUser.State userState = new ParseUser.State.Builder() - .putAuthData(facebookAuthType, facebookAuthData) + .putAuthData(authType, authData) .build(); ParseUser user = ParseObject.from(userState); + user.setIsCurrentUser(true); - user.synchronizeAuthData(provider); + user.synchronizeAuthData(authType); // Make sure we restore authentication - verify(provider, times(1)).restoreAuthentication(facebookAuthData); + verify(provider, times(1)).restoreAuthentication(authData); } @Test