/Parse/src/main/java/com/parse/ParseUser.java
https://gitlab.com/yousafsyed/Parse-SDK-Android · Java · 1401 lines · 861 code · 150 blank · 390 comment · 126 complexity · 57cf5bd6d60540f4236e226707894250 MD5 · raw file
- /*
- * 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.json.JSONObject;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import bolts.Continuation;
- import bolts.Task;
- /**
- * The {@code ParseUser} is a local representation of user data that can be saved and retrieved from
- * the Parse cloud.
- */
- @ParseClassName("_User")
- public class ParseUser extends ParseObject {
- private static final String KEY_SESSION_TOKEN = "sessionToken";
- private static final String KEY_AUTH_DATA = "authData";
- private static final String KEY_USERNAME = "username";
- private static final String KEY_PASSWORD = "password";
- private static final String KEY_EMAIL = "email";
- private static final List<String> READ_ONLY_KEYS = Collections.unmodifiableList(
- Arrays.asList(KEY_SESSION_TOKEN, KEY_AUTH_DATA));
- /**
- * Constructs a query for {@code ParseUser}.
- *
- * @see com.parse.ParseQuery#getQuery(Class)
- */
- public static ParseQuery<ParseUser> getQuery() {
- return ParseQuery.getQuery(ParseUser.class);
- }
- /* package for tests */ static ParseUserController getUserController() {
- return ParseCorePlugins.getInstance().getUserController();
- }
- /* package for tests */ static ParseCurrentUserController getCurrentUserController() {
- 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<Builder> {
- private boolean isNew;
- public Builder() {
- super("_User");
- }
- /* package */ Builder(State state) {
- super(state);
- isNew = state.isNew();
- }
- @Override
- /* package */ Builder self() {
- return this;
- }
- @SuppressWarnings("unchecked")
- @Override
- public State build() {
- return new State(this);
- }
- @Override
- public Builder apply(ParseObject.State other) {
- isNew(((State) other).isNew());
- return super.apply(other);
- }
- public Builder sessionToken(String sessionToken) {
- return put(KEY_SESSION_TOKEN, sessionToken);
- }
- public Builder authData(Map<String, Map<String, String>> authData) {
- return put(KEY_AUTH_DATA, authData);
- }
- @SuppressWarnings("unchecked")
- public Builder putAuthData(String authType, Map<String, String> authData) {
- Map<String, Map<String, String>> newAuthData =
- (Map<String, Map<String, String>>) serverData.get(KEY_AUTH_DATA);
- if (newAuthData == null) {
- newAuthData = new HashMap<>();
- }
- newAuthData.put(authType, authData);
- serverData.put(KEY_AUTH_DATA, newAuthData);
- return this;
- }
- public Builder isNew(boolean isNew) {
- this.isNew = isNew;
- return this;
- }
- }
- private final boolean isNew;
- private State(Builder builder) {
- super(builder);
- isNew = builder.isNew;
- }
- @SuppressWarnings("unchecked")
- @Override
- public Builder newBuilder() {
- return new Builder(this);
- }
- public String sessionToken() {
- return (String) get(KEY_SESSION_TOKEN);
- }
- @SuppressWarnings("unchecked")
- public Map<String, Map<String, String>> authData() {
- Map<String, Map<String, String>> authData =
- (Map<String, Map<String, String>>) get(KEY_AUTH_DATA);
- if (authData == null) {
- // We'll always return non-null for now since we don't have any null checking in place.
- // Be aware not to get and set without checking size or else we'll be adding a value that
- // wasn't there in the first place.
- authData = new HashMap<>();
- }
- return authData;
- }
- public boolean isNew() {
- return isNew;
- }
- }
- // Whether the object is a currentUser. If so, it will always be persisted to disk on updates.
- private boolean isCurrentUser;
- /**
- * Constructs a new ParseUser with no data in it. A ParseUser constructed in this way will not
- * have an objectId and will not persist to the database until {@link #signUp} is called.
- */
- public ParseUser() {
- isCurrentUser = false;
- }
- @Override
- /* package */ boolean needsDefaultACL() {
- return false;
- }
- @Override
- boolean isKeyMutable(String key) {
- return !READ_ONLY_KEYS.contains(key);
- }
- @Override
- /* package */ State.Builder newStateBuilder(String className) {
- return new State.Builder();
- }
- @Override
- /* package */ State getState() {
- return (State) super.getState();
- }
- /**
- * @return {@code true} if this user was created with {@link #getCurrentUser()} when no current
- * user previously existed and {@link #enableAutomaticUser()} is set, false if was created by any
- * other means or if a previously "lazy" user was saved remotely.
- */
- /* package */ boolean isLazy() {
- synchronized (mutex) {
- return getObjectId() == null && ParseAnonymousUtils.isLinked(this);
- }
- }
- /**
- * Whether the ParseUser has been authenticated on this device. This will be true if the ParseUser
- * was obtained via a logIn or signUp method. Only an authenticated ParseUser can be saved (with
- * altered attributes) and deleted.
- */
- public boolean isAuthenticated() {
- synchronized (mutex) {
- ParseUser current = ParseUser.getCurrentUser();
- return isLazy() ||
- (getState().sessionToken() != null
- && current != null
- && getObjectId().equals(current.getObjectId()));
- }
- }
- @Override
- public void remove(String key) {
- if (KEY_USERNAME.equals(key)) {
- throw new IllegalArgumentException("Can't remove the username key.");
- }
- super.remove(key);
- }
- @Override
- /* package */ JSONObject toRest(
- ParseObject.State state,
- List<ParseOperationSet> operationSetQueue,
- ParseEncoder objectEncoder) {
- // Create a sanitized copy of operationSetQueue with `password` removed if necessary
- List<ParseOperationSet> cleanOperationSetQueue = operationSetQueue;
- for (int i = 0; i < operationSetQueue.size(); i++) {
- ParseOperationSet operations = operationSetQueue.get(i);
- if (operations.containsKey(KEY_PASSWORD)) {
- if (cleanOperationSetQueue == operationSetQueue) {
- cleanOperationSetQueue = new LinkedList<>(operationSetQueue);
- }
- ParseOperationSet cleanOperations = new ParseOperationSet(operations);
- cleanOperations.remove(KEY_PASSWORD);
- cleanOperationSetQueue.set(i, cleanOperations);
- }
- }
- return super.toRest(state, cleanOperationSetQueue, objectEncoder);
- }
- /* package for tests */ Task<Void> cleanUpAuthDataAsync() {
- ParseAuthenticationManager controller = getAuthenticationManager();
- Map<String, Map<String, String>> authData;
- synchronized (mutex) {
- authData = getState().authData();
- if (authData.size() == 0) {
- return Task.forResult(null); // Nothing to see or do here...
- }
- }
- List<Task<Void>> tasks = new ArrayList<>();
- Iterator<Map.Entry<String, Map<String, String>>> i = authData.entrySet().iterator();
- while (i.hasNext()) {
- Map.Entry<String, Map<String, String>> entry = i.next();
- if (entry.getValue() == null) {
- i.remove();
- tasks.add(controller.restoreAuthenticationAsync(entry.getKey(), null).makeVoid());
- }
- }
- State newState = getState().newBuilder()
- .authData(authData)
- .build();
- setState(newState);
- return Task.whenAll(tasks);
- }
- @Override
- /* package */ Task<Void> handleSaveResultAsync(
- ParseObject.State result, ParseOperationSet operationsBeforeSave) {
- boolean success = result != null;
- if (success) {
- operationsBeforeSave.remove(KEY_PASSWORD);
- }
- return super.handleSaveResultAsync(result, operationsBeforeSave);
- }
- @Override
- /* package */ void validateSaveEventually() throws ParseException {
- if (isDirty(KEY_PASSWORD)) {
- // TODO(mengyan): Unify the exception we throw when validate fails
- throw new ParseException(
- ParseException.OTHER_CAUSE,
- "Unable to saveEventually on a ParseUser with dirty password");
- }
- }
- //region Getter/Setter helper methods
- /* package */ boolean isCurrentUser() {
- synchronized (mutex) {
- return isCurrentUser;
- }
- }
- /* package */ void setIsCurrentUser(boolean isCurrentUser) {
- synchronized (mutex) {
- this.isCurrentUser = isCurrentUser;
- }
- }
- /**
- * @return the session token for a user, if they are logged in.
- */
- public String getSessionToken() {
- return getState().sessionToken();
- }
- // This is only used when upgrading to revocable session
- private Task<Void> setSessionTokenInBackground(String newSessionToken) {
- synchronized (mutex) {
- State state = getState();
- if (newSessionToken.equals(state.sessionToken())) {
- return Task.forResult(null);
- }
- State.Builder builder = state.newBuilder()
- .sessionToken(newSessionToken);
- setState(builder.build());
- return saveCurrentUserAsync(this);
- }
- }
- /* package for testes */ Map<String, Map<String, String>> getAuthData() {
- synchronized (mutex) {
- Map<String, Map<String, String>> authData = getMap(KEY_AUTH_DATA);
- if (authData == null) {
- // We'll always return non-null for now since we don't have any null checking in place.
- // Be aware not to get and set without checking size or else we'll be adding a value that
- // wasn't there in the first place.
- authData = new HashMap<>();
- }
- return authData;
- }
- }
- private Map<String, String> getAuthData(String authType) {
- return getAuthData().get(authType);
- }
- /* package */ void putAuthData(String authType, Map<String, String> authData) {
- synchronized (mutex) {
- Map<String, Map<String, String>> newAuthData = getAuthData();
- newAuthData.put(authType, authData);
- performPut(KEY_AUTH_DATA, newAuthData);
- }
- }
- private void removeAuthData(String authType) {
- synchronized (mutex) {
- Map<String, Map<String, String>> newAuthData = getAuthData();
- newAuthData.remove(authType);
- performPut(KEY_AUTH_DATA, newAuthData);
- }
- }
- /**
- * Sets the username. Usernames cannot be null or blank.
- *
- * @param username
- * The username to set.
- */
- public void setUsername(String username) {
- put(KEY_USERNAME, username);
- }
- /**
- * Retrieves the username.
- */
- public String getUsername() {
- return getString(KEY_USERNAME);
- }
- /**
- * Sets the password.
- *
- * @param password
- * The password to set.
- */
- public void setPassword(String password) {
- put(KEY_PASSWORD, password);
- }
- /* package for tests */ String getPassword() {
- return getString(KEY_PASSWORD);
- }
- /**
- * Sets the email address.
- *
- * @param email
- * The email address to set.
- */
- public void setEmail(String email) {
- put(KEY_EMAIL, email);
- }
- /**
- * Retrieves the email address.
- */
- public String getEmail() {
- return getString(KEY_EMAIL);
- }
- /**
- * Indicates whether this {@code ParseUser} was created during this session through a call to
- * {@link #signUp()} or by logging in with a linked service such as Facebook.
- */
- public boolean isNew() {
- return getState().isNew();
- }
- //endregion
- @Override
- public void put(String key, Object value) {
- synchronized (mutex) {
- if (KEY_USERNAME.equals(key)) {
- // When the username is set, remove any semblance of anonymity.
- stripAnonymity();
- }
- super.put(key, value);
- }
- }
- private void stripAnonymity() {
- synchronized (mutex) {
- if (ParseAnonymousUtils.isLinked(this)) {
- if (getObjectId() != null) {
- putAuthData(ParseAnonymousUtils.AUTH_TYPE, null);
- } else {
- removeAuthData(ParseAnonymousUtils.AUTH_TYPE);
- }
- }
- }
- }
- // TODO(grantland): Can we replace this with #revert(String)?
- private void restoreAnonymity(Map<String, String> anonymousData) {
- synchronized (mutex) {
- if (anonymousData != null) {
- putAuthData(ParseAnonymousUtils.AUTH_TYPE, anonymousData);
- }
- }
- }
- @Override
- /* package */ void validateSave() {
- synchronized (mutex) {
- if (getObjectId() == null) {
- throw new IllegalArgumentException(
- "Cannot save a ParseUser until it has been signed up. Call signUp first.");
- }
- if (isAuthenticated() || !isDirty() || isCurrentUser()) {
- return;
- }
- }
- if (!Parse.isLocalDatastoreEnabled()) {
- // This might be a different of instance of the currentUser, so we need to check objectIds
- ParseUser current = ParseUser.getCurrentUser(); //TODO (grantland): possible blocking disk i/o
- if (current != null && getObjectId().equals(current.getObjectId())) {
- return;
- }
- }
- throw new IllegalArgumentException("Cannot save a ParseUser that is not authenticated.");
- }
- @Override
- /* package */ Task<Void> saveAsync(String sessionToken, Task<Void> toAwait) {
- return saveAsync(sessionToken, isLazy(), toAwait);
- }
- /* package for tests */ Task<Void> saveAsync(String sessionToken, boolean isLazy, Task<Void> toAwait) {
- Task<Void> task;
- if (isLazy) {
- task = resolveLazinessAsync(toAwait);
- } else {
- task = super.saveAsync(sessionToken, toAwait);
- }
- if (isCurrentUser()) {
- // If the user is the currently logged in user, we persist all data to disk
- return task.onSuccessTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return cleanUpAuthDataAsync();
- }
- }).onSuccessTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return saveCurrentUserAsync(ParseUser.this);
- }
- });
- }
- return task;
- }
- @Override
- /* package */ void setState(ParseObject.State newState) {
- if (isCurrentUser()) {
- State.Builder newStateBuilder = newState.newBuilder();
- // Avoid clearing sessionToken when updating the current user's State via ParseQuery result
- if (getSessionToken() != null && newState.get(KEY_SESSION_TOKEN) == null) {
- newStateBuilder.put(KEY_SESSION_TOKEN, getSessionToken());
- }
- // Avoid clearing authData when updating the current user's State via ParseQuery result
- if (getAuthData().size() > 0 && newState.get(KEY_AUTH_DATA) == null) {
- newStateBuilder.put(KEY_AUTH_DATA, getAuthData());
- }
- newState = newStateBuilder.build();
- }
- super.setState(newState);
- }
- @Override
- /* package */ void validateDelete() {
- synchronized (mutex) {
- super.validateDelete();
- if (!isAuthenticated() && isDirty()) {
- throw new IllegalArgumentException("Cannot delete a ParseUser that is not authenticated.");
- }
- }
- }
- @SuppressWarnings("unchecked")
- @Override
- public ParseUser fetch() throws ParseException {
- return (ParseUser) super.fetch();
- }
- @SuppressWarnings("unchecked")
- @Override
- /* package */ <T extends ParseObject> Task<T> fetchAsync(
- String sessionToken, Task<Void> toAwait) {
- //TODO (grantland): It doesn't seem like we should do this.. Why don't we error like we do
- // when fetching an unsaved ParseObject?
- if (isLazy()) {
- return Task.forResult((T) this);
- }
- Task<T> task = super.fetchAsync(sessionToken, toAwait);
- if (isCurrentUser()) {
- return task.onSuccessTask(new Continuation<T, Task<Void>>() {
- @Override
- public Task<Void> then(final Task<T> fetchAsyncTask) throws Exception {
- return cleanUpAuthDataAsync();
- }
- }).onSuccessTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return saveCurrentUserAsync(ParseUser.this);
- }
- }).onSuccess(new Continuation<Void, T>() {
- @Override
- public T then(Task<Void> task) throws Exception {
- return (T) ParseUser.this;
- }
- });
- }
- return task;
- }
- /**
- * Signs up a new user. You should call this instead of {@link #save} for new ParseUsers. This
- * will create a new ParseUser on the server, and also persist the session on disk so that you can
- * access the user using {@link #getCurrentUser}.
- * <p/>
- * A username and password must be set before calling signUp.
- * <p/>
- * This is preferable to using {@link #signUp}, unless your code is already running from a
- * background thread.
- *
- * @return A Task that is resolved when sign up completes.
- */
- public Task<Void> signUpInBackground() {
- return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return signUpAsync(task);
- }
- });
- }
- /* package for tests */ Task<Void> signUpAsync(Task<Void> toAwait) {
- final ParseUser user = getCurrentUser(); //TODO (grantland): convert to async
- synchronized (mutex) {
- final String sessionToken = user != null ? user.getSessionToken() : null;
- if (ParseTextUtils.isEmpty(getUsername())) {
- return Task.forError(new IllegalArgumentException("Username cannot be missing or blank"));
- }
- if (ParseTextUtils.isEmpty(getPassword())) {
- return Task.forError(new IllegalArgumentException("Password cannot be missing or blank"));
- }
- if (getObjectId() != null) {
- // For anonymous users, there may be an objectId. Setting the
- // userName will have removed the anonymous link and set the
- // value in the authData object to JSONObject.NULL, so we can
- // just treat it like a save operation.
- Map<String, Map<String, String>> authData = getAuthData();
- if (authData.containsKey(ParseAnonymousUtils.AUTH_TYPE)
- && authData.get(ParseAnonymousUtils.AUTH_TYPE) == null) {
- return saveAsync(sessionToken, toAwait);
- }
- // Otherwise, throw.
- return Task.forError(
- new IllegalArgumentException("Cannot sign up a user that has already signed up."));
- }
- // If the operationSetQueue is has operation sets in it, then a save or signUp is in progress.
- // If there is a signUp or save already in progress, don't allow another one to start.
- if (operationSetQueue.size() > 1) {
- return Task.forError(
- new IllegalArgumentException("Cannot sign up a user that is already signing up."));
- }
- // If the current user is an anonymous user, merge this object's data into the anonymous user
- // and save.
- if (user != null && ParseAnonymousUtils.isLinked(user)) {
- // this doesn't have any outstanding saves, so we can safely merge its operations into the
- // current user.
- if (this == user) {
- return Task.forError(
- new IllegalArgumentException("Attempt to merge currentUser with itself."));
- }
- boolean isLazy = user.isLazy();
- final String oldUsername = user.getUsername();
- final String oldPassword = user.getPassword();
- final Map<String, String> anonymousData = user.getAuthData(ParseAnonymousUtils.AUTH_TYPE);
- user.copyChangesFrom(this);
- user.setUsername(getUsername());
- user.setPassword(getPassword());
- revert();
- return user.saveAsync(sessionToken, isLazy, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- if (task.isCancelled() || task.isFaulted()) { // Error
- synchronized (user.mutex) {
- if (oldUsername != null) {
- user.setUsername(oldUsername);
- } else {
- user.revert(KEY_USERNAME);
- }
- if (oldPassword != null) {
- user.setPassword(oldPassword);
- } else {
- user.revert(KEY_PASSWORD);
- }
- user.restoreAnonymity(anonymousData);
- }
- return task;
- } else { // Success
- user.revert(KEY_PASSWORD);
- revert(KEY_PASSWORD);
- }
- mergeFromObject(user);
- return saveCurrentUserAsync(ParseUser.this);
- }
- });
- }
- final ParseOperationSet operations = startSave();
- return toAwait.onSuccessTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return getUserController().signUpAsync(
- getState(), operations, sessionToken
- ).continueWithTask(new Continuation<ParseUser.State, Task<Void>>() {
- @Override
- public Task<Void> then(final Task<ParseUser.State> signUpTask) throws Exception {
- ParseUser.State result = signUpTask.getResult();
- return handleSaveResultAsync(result,
- operations).continueWithTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- if (!signUpTask.isCancelled() && !signUpTask.isFaulted()) {
- return saveCurrentUserAsync(ParseUser.this);
- }
- return signUpTask.makeVoid();
- }
- });
- }
- });
- }
- });
- }
- }
- /**
- * Signs up a new user. You should call this instead of {@link #save} for new ParseUsers. This
- * will create a new ParseUser on the server, and also persist the session on disk so that you can
- * access the user using {@link #getCurrentUser}.
- * <p/>
- * A username and password must be set before calling signUp.
- * <p/>
- * Typically, you should use {@link #signUpInBackground} instead of this, unless you are managing
- * your own threading.
- *
- * @throws ParseException
- * Throws an exception if the server is inaccessible, or if the username has already
- * been taken.
- */
- public void signUp() throws ParseException {
- ParseTaskUtils.wait(signUpInBackground());
- }
- /**
- * Signs up a new user. You should call this instead of {@link #save} for new ParseUsers. This
- * will create a new ParseUser on the server, and also persist the session on disk so that you can
- * access the user using {@link #getCurrentUser}.
- * <p/>
- * A username and password must be set before calling signUp.
- * <p/>
- * This is preferable to using {@link #signUp}, unless your code is already running from a
- * background thread.
- *
- * @param callback
- * callback.done(user, e) is called when the signUp completes.
- */
- public void signUpInBackground(SignUpCallback callback) {
- ParseTaskUtils.callbackOnMainThreadAsync(signUpInBackground(), callback);
- }
- /**
- * Logs in a user with a username and password. On success, this saves the session to disk, so you
- * can retrieve the currently logged in user using {@link #getCurrentUser}.
- * <p/>
- * This is preferable to using {@link #logIn}, unless your code is already running from a
- * background thread.
- *
- * @param username
- * The username to log in with.
- * @param password
- * The password to log in with.
- *
- * @return A Task that is resolved when logging in completes.
- */
- public static Task<ParseUser> logInInBackground(String username, String password) {
- if (username == null) {
- throw new IllegalArgumentException("Must specify a username for the user to log in with");
- }
- if (password == null) {
- throw new IllegalArgumentException("Must specify a password for the user to log in with");
- }
- return getUserController().logInAsync(username, password).onSuccessTask(new Continuation<State, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<State> task) throws Exception {
- State result = task.getResult();
- final ParseUser newCurrent = ParseObject.from(result);
- return saveCurrentUserAsync(newCurrent).onSuccess(new Continuation<Void, ParseUser>() {
- @Override
- public ParseUser then(Task<Void> task) throws Exception {
- return newCurrent;
- }
- });
- }
- });
- }
- /**
- * Logs in a user with a username and password. On success, this saves the session to disk, so you
- * can retrieve the currently logged in user using {@link #getCurrentUser}.
- * <p/>
- * Typically, you should use {@link #logInInBackground} instead of this, unless you are managing
- * your own threading.
- *
- * @param username
- * The username to log in with.
- * @param password
- * The password to log in with.
- * @throws ParseException
- * Throws an exception if the login was unsuccessful.
- * @return The user if the login was successful.
- */
- public static ParseUser logIn(String username, String password) throws ParseException {
- return ParseTaskUtils.wait(logInInBackground(username, password));
- }
- /**
- * Logs in a user with a username and password. On success, this saves the session to disk, so you
- * can retrieve the currently logged in user using {@link #getCurrentUser}.
- * <p/>
- * This is preferable to using {@link #logIn}, unless your code is already running from a
- * background thread.
- *
- * @param username
- * The username to log in with.
- * @param password
- * The password to log in with.
- * @param callback
- * callback.done(user, e) is called when the login completes.
- */
- public static void logInInBackground(final String username, final String password,
- LogInCallback callback) {
- ParseTaskUtils.callbackOnMainThreadAsync(logInInBackground(username, password), callback);
- }
- /**
- * Authorize a user with a session token. On success, this saves the session to disk, so you can
- * retrieve the currently logged in user using {@link #getCurrentUser}.
- * <p/>
- * This is preferable to using {@link #become}, unless your code is already running from a
- * background thread.
- *
- * @param sessionToken
- * The session token to authorize with.
- *
- * @return A Task that is resolved when authorization completes.
- */
- public static Task<ParseUser> becomeInBackground(String sessionToken) {
- if (sessionToken == null) {
- throw new IllegalArgumentException("Must specify a sessionToken for the user to log in with");
- }
- return getUserController().getUserAsync(sessionToken).onSuccessTask(new Continuation<State, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<State> task) throws Exception {
- State result = task.getResult();
- final ParseUser user = ParseObject.from(result);
- return saveCurrentUserAsync(user).onSuccess(new Continuation<Void, ParseUser>() {
- @Override
- public ParseUser then(Task<Void> task) throws Exception {
- return user;
- }
- });
- }
- });
- }
- /**
- * Authorize a user with a session token. On success, this saves the session to disk, so you can
- * retrieve the currently logged in user using {@link #getCurrentUser}.
- * <p/>
- * Typically, you should use {@link #becomeInBackground} instead of this, unless you are managing
- * your own threading.
- *
- * @param sessionToken
- * The session token to authorize with.
- * @throws ParseException
- * Throws an exception if the authorization was unsuccessful.
- * @return The user if the authorization was successful.
- */
- public static ParseUser become(String sessionToken) throws ParseException {
- return ParseTaskUtils.wait(becomeInBackground(sessionToken));
- }
- /**
- * Authorize a user with a session token. On success, this saves the session to disk, so you can
- * retrieve the currently logged in user using {@link #getCurrentUser}.
- * <p/>
- * This is preferable to using {@link #become}, unless your code is already running from a
- * background thread.
- *
- * @param sessionToken
- * The session token to authorize with.
- * @param callback
- * callback.done(user, e) is called when the authorization completes.
- */
- public static void becomeInBackground(final String sessionToken, LogInCallback callback) {
- ParseTaskUtils.callbackOnMainThreadAsync(becomeInBackground(sessionToken), callback);
- }
- //TODO (grantland): Publicize
- /* package */ static Task<ParseUser> getCurrentUserAsync() {
- return getCurrentUserController().getAsync();
- }
- /**
- * This retrieves the currently logged in ParseUser with a valid session, either from memory or
- * disk if necessary.
- *
- * @return The currently logged in ParseUser
- */
- public static ParseUser getCurrentUser() {
- return getCurrentUser(isAutomaticUserEnabled());
- }
- /**
- * This retrieves the currently logged in ParseUser with a valid session, either from memory or
- * disk if necessary.
- *
- * @param shouldAutoCreateUser
- * {@code true} to automatically create and set an anonymous user as current.
- * @return The currently logged in ParseUser
- */
- private static ParseUser getCurrentUser(boolean shouldAutoCreateUser) {
- try {
- return ParseTaskUtils.wait(getCurrentUserController().getAsync(shouldAutoCreateUser));
- } catch (ParseException e) {
- //TODO (grantland): Publicize this exception
- return null;
- }
- }
- //TODO (grantland): Make it throw ParseException and call #getCurrenSessionTokenInBackground()
- /* package */ static String getCurrentSessionToken() {
- ParseUser current = ParseUser.getCurrentUser();
- return current != null ? current.getSessionToken() : null;
- }
- //TODO (grantland): Make it really async and publicize in v2
- /* package */ static Task<String> getCurrentSessionTokenAsync() {
- return getCurrentUserController().getCurrentSessionTokenAsync();
- }
- // Persists a user as currentUser to disk, and into the singleton
- private static Task<Void> saveCurrentUserAsync(ParseUser user) {
- return getCurrentUserController().setAsync(user);
- }
- /**
- * Used by {@link ParseObject#pin} to persist lazy users to LDS that haven't been pinned yet.
- */
- /* package */ static Task<Void> pinCurrentUserIfNeededAsync(ParseUser user) {
- if (!Parse.isLocalDatastoreEnabled()) {
- throw new IllegalStateException("Method requires Local Datastore. " +
- "Please refer to `Parse#enableLocalDatastore(Context)`.");
- }
- return getCurrentUserController().setIfNeededAsync(user);
- }
- /**
- * Logs out the currently logged in user session. This will remove the session from disk, log out
- * of linked services, and future calls to {@link #getCurrentUser()} will return {@code null}.
- * <p/>
- * This is preferable to using {@link #logOut}, unless your code is already running from a
- * background thread.
- *
- * @return A Task that is resolved when logging out completes.
- */
- public static Task<Void> logOutInBackground() {
- return getCurrentUserController().logOutAsync();
- }
- /**
- * Logs out the currently logged in user session. This will remove the session from disk, log out
- * of linked services, and future calls to {@link #getCurrentUser()} will return {@code null}.
- * <p/>
- * This is preferable to using {@link #logOut}, unless your code is already running from a
- * background thread.
- */
- public static void logOutInBackground(LogOutCallback callback) {
- ParseTaskUtils.callbackOnMainThreadAsync(logOutInBackground(), callback);
- }
- /**
- * Logs out the currently logged in user session. This will remove the session from disk, log out
- * of linked services, and future calls to {@link #getCurrentUser()} will return {@code null}.
- * <p/>
- * Typically, you should use {@link #logOutInBackground()} instead of this, unless you are
- * managing your own threading.
- * <p/>
- * <strong>Note:</strong>: Any errors in the log out flow will be swallowed due to
- * backward-compatibility reasons. Please use {@link #logOutInBackground()} if you'd wish to
- * handle them.
- */
- public static void logOut() {
- try {
- ParseTaskUtils.wait(logOutInBackground());
- } catch (ParseException e) {
- //TODO (grantland): We shouldn't swallow errors, but we need to for backwards compatibility.
- // Change this in v2.
- }
- }
- //TODO (grantland): Add to taskQueue
- /* package */ Task<Void> logOutAsync() {
- return logOutAsync(true);
- }
- /* package */ Task<Void> logOutAsync(boolean revoke) {
- ParseAuthenticationManager controller = getAuthenticationManager();
- List<Task<Void>> tasks = new ArrayList<>();
- final String oldSessionToken;
- synchronized (mutex) {
- oldSessionToken = getState().sessionToken();
- for (Map.Entry<String, Map<String, String>> entry : getAuthData().entrySet()) {
- tasks.add(controller.deauthenticateAsync(entry.getKey()));
- }
- State newState = getState().newBuilder()
- .sessionToken(null)
- .isNew(false)
- .build();
- isCurrentUser = false;
- setState(newState);
- }
- if (revoke) {
- tasks.add(ParseSession.revokeAsync(oldSessionToken));
- }
- return Task.whenAll(tasks);
- }
- /**
- * Requests a password reset email to be sent in a background thread to the specified email
- * address associated with the user account. This email allows the user to securely reset their
- * password on the Parse site.
- * <p/>
- * This is preferable to using {@link #requestPasswordReset(String)}, unless your code is already
- * running from a background thread.
- *
- * @param email
- * The email address associated with the user that forgot their password.
- *
- * @return A Task that is resolved when the command completes.
- */
- public static Task<Void> requestPasswordResetInBackground(String email) {
- return getUserController().requestPasswordResetAsync(email);
- }
- /**
- * Requests a password reset email to be sent to the specified email address associated with the
- * user account. This email allows the user to securely reset their password on the Parse site.
- * <p/>
- * Typically, you should use {@link #requestPasswordResetInBackground} instead of this, unless you
- * are managing your own threading.
- *
- * @param email
- * The email address associated with the user that forgot their password.
- * @throws ParseException
- * Throws an exception if the server is inaccessible, or if an account with that email
- * doesn't exist.
- */
- public static void requestPasswordReset(String email) throws ParseException {
- ParseTaskUtils.wait(requestPasswordResetInBackground(email));
- }
- /**
- * Requests a password reset email to be sent in a background thread to the specified email
- * address associated with the user account. This email allows the user to securely reset their
- * password on the Parse site.
- * <p/>
- * This is preferable to using {@link #requestPasswordReset(String)}, unless your code is already
- * running from a background thread.
- *
- * @param email
- * The email address associated with the user that forgot their password.
- * @param callback
- * callback.done(e) is called when the request completes.
- */
- public static void requestPasswordResetInBackground(final String email,
- RequestPasswordResetCallback callback) {
- ParseTaskUtils.callbackOnMainThreadAsync(requestPasswordResetInBackground(email), callback);
- }
- @SuppressWarnings("unchecked")
- @Override
- public ParseUser fetchIfNeeded() throws ParseException {
- return super.fetchIfNeeded();
- }
- //region Third party authentication
- /**
- * Registers a third party authentication callback.
- * <p />
- * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
- * library.
- *
- * @param authType The name of the third party authentication source.
- * @param callback The third party authentication callback to be registered.
- *
- * @see AuthenticationCallback
- */
- public static void registerAuthenticationCallback(
- String authType, AuthenticationCallback callback) {
- getAuthenticationManager().register(authType, callback);
- }
- /**
- * Logs in a user with third party authentication credentials.
- * <p />
- * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
- * library.
- *
- * @param authType The name of the third party authentication source.
- * @param authData The user credentials of the third party authentication source.
- * @return A {@code Task} is resolved when logging in completes.
- *
- * @see AuthenticationCallback
- */
- public static Task<ParseUser> logInWithInBackground(
- final String authType, final Map<String, String> authData) {
- if (authType == null) {
- throw new IllegalArgumentException("Invalid authType: " + null);
- }
- final Continuation<Void, Task<ParseUser>> logInWithTask = new Continuation<Void, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<Void> task) throws Exception {
- return getUserController().logInAsync(authType, authData).onSuccessTask(new Continuation<ParseUser.State, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<ParseUser.State> task) throws Exception {
- ParseUser.State result = task.getResult();
- final ParseUser user = ParseObject.from(result);
- return saveCurrentUserAsync(user).onSuccess(new Continuation<Void, ParseUser>() {
- @Override
- public ParseUser then(Task<Void> task) throws Exception {
- return user;
- }
- });
- }
- });
- }
- };
- // Handle claiming of user.
- return getCurrentUserController().getAsync(false).onSuccessTask(new Continuation<ParseUser, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<ParseUser> task) throws Exception {
- final ParseUser user = task.getResult();
- if (user != null) {
- synchronized (user.mutex) {
- if (ParseAnonymousUtils.isLinked(user)) {
- if (user.isLazy()) {
- final Map<String, String> oldAnonymousData =
- user.getAuthData(ParseAnonymousUtils.AUTH_TYPE);
- return user.taskQueue.enqueue(new Continuation<Void, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(final Task<Void> toAwait) throws Exception {
- return toAwait.continueWithTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- synchronized (user.mutex) {
- // Replace any anonymity with the new linked authData.
- user.stripAnonymity();
- user.putAuthData(authType, authData);
- return user.resolveLazinessAsync(task);
- }
- }
- }).continueWithTask(new Continuation<Void, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<Void> task) throws Exception {
- synchronized (user.mutex) {
- if (task.isFaulted()) {
- user.removeAuthData(authType);
- user.restoreAnonymity(oldAnonymousData);
- return Task.forError(task.getError());
- }
- if (task.isCancelled()) {
- return Task.cancelled();
- }
- return Task.forResult(user);
- }
- }
- });
- }
- });
- } else {
- // 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.linkWithInBackground(authType, authData)
- .continueWithTask(new Continuation<Void, Task<ParseUser>>() {
- @Override
- public Task<ParseUser> then(Task<Void> task) throws Exception {
- if (task.isFaulted()) {
- Exception error = task.getError();
- if (error instanceof ParseException
- && ((ParseException) error).getCode() == ParseException.ACCOUNT_ALREADY_LINKED) {
- // An account that's linked to the given authData already exists, so log in
- // instead of trying to claim.
- return Task.<Void>forResult(null).continueWithTask(logInWithTask);
- }
- }
- if (task.isCancelled()) {
- return Task.cancelled();
- }
- return Task.forResult(user);
- }
- });
- }
- }
- }
- }
- return Task.<Void>forResult(null).continueWithTask(logInWithTask);
- }
- });
- }
- /**
- * Indicates whether this user is linked with a third party authentication source.
- * <p />
- * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
- * library.
- *
- * @param authType The name of the third party authentication source.
- * @return {@code true} if linked, otherwise {@code false}.
- *
- * @see AuthenticationCallback
- */
- public boolean isLinked(String authType) {
- Map<String, Map<String, String>> authData = getAuthData();
- return authData.containsKey(authType) && authData.get(authType) != null;
- }
- /**
- * Ensures that all auth sources have auth data (e.g. access tokens, etc.) that matches this
- * user.
- */
- /* package */ Task<Void> synchronizeAllAuthDataAsync() {
- Map<String, Map<String, String>> authData;
- synchronized (mutex) {
- if (!isCurrentUser()) {
- return Task.forResult(null);
- }
- authData = getAuthData();
- }
- List<Task<Void>> tasks = new ArrayList<>(authData.size());
- for (String authType : authData.keySet()) {
- tasks.add(synchronizeAuthDataAsync(authType));
- }
- return Task.whenAll(tasks);
- }
- /* package */ Task<Void> synchronizeAuthDataAsync(String authType) {
- Map<String, String> authData;
- synchronized (mutex) {
- if (!isCurrentUser()) {
- return Task.forResult(null);
- }
- authData = getAuthData(authType);
- }
- return synchronizeAuthDataAsync(getAuthenticationManager(), authType, authData);
- }
- private Task<Void> synchronizeAuthDataAsync(
- ParseAuthenticationManager manager, final String authType, Map<String, String> authData) {
- return manager.restoreAuthenticationAsync(authType, authData).continueWithTask(new Continuation<Boolean, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Boolean> task) throws Exception {
- boolean success = !task.isFaulted() && task.getResult();
- if (!success) {
- return unlinkFromInBackground(authType);
- }
- return task.makeVoid();
- }
- });
- }
- private Task<Void> linkWithAsync(
- final String authType,
- final Map<String, String> authData,
- final Task<Void> toAwait,
- final String sessionToken) {
- synchronized (mutex) {
- final boolean isLazy = isLazy();
- final Map<String, String> oldAnonymousData = getAuthData(ParseAnonymousUtils.AUTH_TYPE);
- stripAnonymity();
- putAuthData(authType, authData);
- return saveAsync(sessionToken, isLazy, toAwait).continueWithTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- synchronized (mutex) {
- if (task.isFaulted() || task.isCancelled()) {
- restoreAnonymity(oldAnonymousData);
- return task;
- }
- return synchronizeAuthDataAsync(authType);
- }
- }
- });
- }
- }
- private Task<Void> linkWithAsync(
- final String authType,
- final Map<String, String> authData,
- final String sessionToken) {
- return taskQueue.enqueue(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return linkWithAsync(authType, authData, task, sessionToken);
- }
- });
- }
- /**
- * Links this user to a third party authentication source.
- * <p />
- * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
- * library.
- *
- * @param authType The name of the third party authentication source.
- * @param authData The user credentials of the third party authentication source.
- * @return A {@code Task} is resolved when linking completes.
- *
- * @see AuthenticationCallback
- */
- public Task<Void> linkWithInBackground(
- String authType, Map<String, String> authData) {
- if (authType == null) {
- throw new IllegalArgumentException("Invalid authType: " + null);
- }
- return linkWithAsync(authType, authData, getSessionToken());
- }
- /**
- * Unlinks this user from a third party authentication source.
- * <p />
- * <strong>Note:</strong> This shouldn't be called directly unless developing a third party authentication
- * library.
- *
- * @param authType The name of the third party authentication source.
- * @return A {@code Task} is resolved when unlinking completes.
- *
- * @see AuthenticationCallback
- */
- public Task<Void> unlinkFromInBackground(final String authType) {
- if (authType == null) {
- return Task.forResult(null);
- }
- synchronized (mutex) {
- if (!getAuthData().containsKey(authType)) {
- return Task.forResult(null);
- }
- putAuthData(authType, null);
- }
- return saveInBackground();
- }
- //endregion
- /**
- * Try to resolve a lazy user.
- *
- * If {@code authData} is empty, we'll treat this just as a SignUp. Otherwise, we'll
- * treat this as a SignUpOrLogIn. We'll merge the server result with this user, only if LDS is not
- * enabled.
- *
- * @param toAwait {@code Task} to wait for completion before running.
- * @return A {@code Task} that will resolve to the current user. If this is a SignUp it'll be this
- * {@code ParseUser} instance, otherwise it'll be a new {@code ParseUser} instance.
- */
- /* package for tests */ Task<Void> resolveLazinessAsync(Task<Void> toAwait) {
- synchronized (mutex) {
- if (getAuthData().size() == 0) { // TODO(grantland): Could we just check isDirty(KEY_AUTH_DATA)?
- // If there are no linked services, treat this as a SignUp.
- return signUpAsync(toAwait);
- }
- final ParseOperationSet operations = startSave();
- // Otherwise, treat this as a SignUpOrLogIn
- return toAwait.onSuccessTask(new Continuation<Void, Task<Void>>() {
- @Override
- public Task<Void> then(Task<Void> task) throws Exception {
- return getUserController().logInAsync(getState(), operations).onSuccessTask(new Continuation<ParseUser.State, Task<Void>>() {
- @Override
- public Task<Void> then(Task<ParseUser.State> task) throws Exception {
- final ParseUser.State result = task.getResult();
- Task<ParseUser.State> resultTask;
- // We can't merge this user with the server if this is a LogIn because LDS might
- // already be keeping track of the servers objectId.
- if (Parse.isLocalDatastoreEnabled() && !result.isNew()) {
- resultTask = Task.forResult(result);
- } else {
- resultTask = handleSaveResultAsync(result,
- operations).onSuccess(new Continuation<Void, ParseUser.State>() {
- @Override
- public ParseUser.State then(Task<Void> task) throws Exception {
- return result;
- }
- });
- }
- return resultTask.onSuccessTask(new Continuation<ParseUser.State, Task<Void>>() {
- @Override
- public Task<Void> then(Task<ParseUser.State> task) throws Exception {
- ParseUser.State result = task.getResult();
- if (!result.isNew()) {
- // If the result is not a new user, treat this as a fresh logIn with complete
- // serverData, and switch the current user to the new user.
- final ParseUser newUser = ParseObjec