Revert "Merge branch 'backend-overhaul' into master" (#3125)

* Revert "Merge branch 'backend-overhaul' into master"

This reverts commit 0090f24257, reversing
changes made to 9bccbfe443.

* fixed test handleSubmitTest
This commit is contained in:
Ashish Kumar 2019-08-12 14:32:25 +05:30 committed by Vivek Maskara
parent cbdfb05530
commit 5865d59d22
77 changed files with 3471 additions and 1816 deletions

View file

@ -3,8 +3,8 @@ package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
import timber.log.Timber;
@ -12,8 +12,10 @@ public class AccountUtil {
public static final String AUTH_COOKIE = "authCookie";
public static final String AUTH_TOKEN_TYPE = "CommonsAndroid";
private final Context context;
public AccountUtil() {
public AccountUtil(Context context) {
this.context = context;
}
/**
@ -47,4 +49,5 @@ public class AccountUtil {
private static AccountManager accountManager(Context context) {
return AccountManager.get(context);
}
}

View file

@ -12,19 +12,48 @@ import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_COOKIE;
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
@Inject
protected SessionManager sessionManager;
@Inject
MediaWikiApi mediaWikiApi;
private String authCookie;
protected void requestAuthToken() {
if (authCookie != null) {
onAuthCookieAcquired(authCookie);
return;
}
authCookie = sessionManager.getAuthCookie();
if (authCookie != null) {
onAuthCookieAcquired(authCookie);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
authCookie = savedInstanceState.getString(AUTH_COOKIE);
}
showBlockStatus();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(AUTH_COOKIE, authCookie);
}
protected abstract void onAuthCookieAcquired(String authCookie);
protected abstract void onAuthFailure();
/**
* Makes API call to check if user is blocked from Commons. If the user is blocked, a snackbar
* is created to notify the user

View file

@ -1,6 +1,9 @@
package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
@ -17,6 +20,14 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.google.android.material.textfield.TextInputLayout;
import java.io.IOException;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
@ -24,17 +35,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.NavUtils;
import androidx.core.content.ContextCompat;
import com.google.android.material.textfield.TextInputLayout;
import org.wikipedia.AppAdapter;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.login.LoginClient;
import org.wikipedia.login.LoginResult;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -52,17 +52,16 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Action;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_WIKI_SITE;
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
public class LoginActivity extends AccountAuthenticatorActivity {
@ -72,17 +71,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Inject
SessionManager sessionManager;
@Inject
@Named(NAMED_COMMONS_WIKI_SITE)
WikiSite commonsWikiSite;
@Inject
@Named("default_preferences")
JsonKvStore applicationKvStore;
@Inject
LoginClient loginClient;
@BindView(R.id.login_button)
Button loginButton;
@ -112,6 +104,13 @@ public class LoginActivity extends AccountAuthenticatorActivity {
private LoginTextWatcher textWatcher = new LoginTextWatcher();
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private Boolean loginCurrentlyInProgress = false;
private Boolean errorMessageShown = false;
private String resultantError;
private static final String RESULTANT_ERROR = "resultantError";
private static final String ERROR_MESSAGE_SHOWN = "errorMessageShown";
private static final String LOGGING_IN = "loggingIn";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -212,8 +211,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
}
if (sessionManager.getCurrentAccount() != null
&& sessionManager.isUserLoggedIn()) {
&& sessionManager.isUserLoggedIn()
&& sessionManager.getCachedAuthCookie() != null) {
applicationKvStore.putBoolean("login_skipped", false);
sessionManager.revalidateAuthToken();
startMainActivity();
}
@ -243,6 +244,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@OnClick(R.id.login_button)
public void performLogin() {
loginCurrentlyInProgress = true;
Timber.d("Login to start!");
final String username = usernameEdit.getText().toString();
final String rawUsername = usernameEdit.getText().toString().trim();
@ -250,37 +252,23 @@ public class LoginActivity extends AccountAuthenticatorActivity {
String twoFactorCode = twoFactorEdit.getText().toString();
showLoggingProgressBar();
doLogin(username, password, twoFactorCode);
}
private void doLogin(String username, String password, String twoFactorCode) {
progressDialog.show();
Action action = () -> {
try {
loginClient.loginBlocking(commonsWikiSite, username, password, twoFactorCode);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
};
compositeDisposable.add(Completable.fromAction(action)
compositeDisposable.add(Observable.fromCallable(() -> login(username, password, twoFactorCode))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> onLoginSuccess(username, password),
error -> {
if (error instanceof LoginClient.LoginFailedException) {
LoginClient.LoginFailedException exception = (LoginClient.LoginFailedException) error;
if (exception.getMessage().equals("2FA")) {
askUserForTwoFactorAuth();
}
}
if (!progressDialog.isShowing()) {
return;
}
progressDialog.dismiss();
showMessageAndCancelDialog(R.string.error_occurred);
}));
.subscribe(result -> handleLogin(username, rawUsername, password, result)));
}
private String login(String username, String password, String twoFactorCode) {
try {
if (twoFactorCode.isEmpty()) {
return mwApi.login(username, password);
} else {
return mwApi.login(username, password, twoFactorCode);
}
} catch (IOException e) {
// Do something better!
return "NetworkFailure";
}
}
/**
@ -293,6 +281,18 @@ public class LoginActivity extends AccountAuthenticatorActivity {
finish();
}
private void handleLogin(String username, String rawUsername, String password, String result) {
Timber.d("Login done!");
if (result.equals("PASS")) {
handlePassResult(username, rawUsername, password);
} else {
loginCurrentlyInProgress = false;
errorMessageShown = true;
resultantError = result;
handleOtherResults(result);
}
}
private void showLoggingProgressBar() {
progressDialog = new ProgressDialog(this);
progressDialog.setIndeterminate(true);
@ -302,19 +302,67 @@ public class LoginActivity extends AccountAuthenticatorActivity {
progressDialog.show();
}
private void onLoginSuccess(String username, String password) {
if (!progressDialog.isShowing()) {
// no longer attached to activity!
return;
}
sessionManager.setUserLoggedIn(true);
LoginResult loginResult = new LoginResult(commonsWikiSite, "PASS", username, password, "");
AppAdapter.get().updateAccount(loginResult);
progressDialog.dismiss();
private void handlePassResult(String username, String rawUsername, String password) {
showSuccessAndDismissDialog();
requestAuthToken();
AccountAuthenticatorResponse response = null;
Bundle extras = getIntent().getExtras();
if (extras != null) {
Timber.d("Bundle of extras: %s", extras);
response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
if (response != null) {
Bundle authResult = new Bundle();
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, BuildConfig.ACCOUNT_TYPE);
response.onResult(authResult);
}
}
sessionManager.createAccount(response, username, rawUsername, password);
startMainActivity();
}
protected void requestAuthToken() {
AccountManager accountManager = AccountManager.get(this);
Account curAccount = sessionManager.getCurrentAccount();
if (curAccount != null) {
accountManager.setAuthToken(curAccount, AUTH_TOKEN_TYPE, mwApi.getAuthCookie());
}
}
/**
* Match known failure message codes and provide messages.
*
* @param result String
*/
private void handleOtherResults(String result) {
if (result.equals("NetworkFailure")) {
// Matches NetworkFailure which is created by the doInBackground method
showMessageAndCancelDialog(R.string.login_failed_network);
} else if (result.toLowerCase(Locale.getDefault()).contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
// Matches nosuchuser, nosuchusershort, noname
showMessageAndCancelDialog(R.string.login_failed_wrong_credentials);
emptySensitiveEditFields();
} else if (result.toLowerCase(Locale.getDefault()).contains("wrongpassword".toLowerCase())) {
// Matches wrongpassword, wrongpasswordempty
showMessageAndCancelDialog(R.string.login_failed_wrong_credentials);
emptySensitiveEditFields();
} else if (result.toLowerCase(Locale.getDefault()).contains("throttle".toLowerCase())) {
// Matches unknown throttle error codes
showMessageAndCancelDialog(R.string.login_failed_throttled);
} else if (result.toLowerCase(Locale.getDefault()).contains("userblocked".toLowerCase())) {
// Matches login-userblocked
showMessageAndCancelDialog(R.string.login_failed_blocked);
} else if (result.equals("2FA")) {
askUserForTwoFactorAuth();
} else {
// Occurs with unhandled login failure codes
Timber.d("Login failed with reason: %s", result);
showMessageAndCancelDialog(R.string.login_failed_generic);
}
}
@Override
protected void onStart() {
super.onStart();
@ -354,6 +402,30 @@ public class LoginActivity extends AccountAuthenticatorActivity {
return getDelegate().getMenuInflater();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(LOGGING_IN, loginCurrentlyInProgress);
outState.putBoolean(ERROR_MESSAGE_SHOWN, errorMessageShown);
outState.putString(RESULTANT_ERROR, resultantError);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
loginCurrentlyInProgress = savedInstanceState.getBoolean(LOGGING_IN, false);
errorMessageShown = savedInstanceState.getBoolean(ERROR_MESSAGE_SHOWN, false);
if (loginCurrentlyInProgress) {
performLogin();
}
if (errorMessageShown) {
resultantError = savedInstanceState.getString(RESULTANT_ERROR);
if (resultantError != null) {
handleOtherResults(resultantError);
}
}
}
public void askUserForTwoFactorAuth() {
progressDialog.dismiss();
twoFactorContainer.setVisibility(VISIBLE);
@ -368,18 +440,16 @@ public class LoginActivity extends AccountAuthenticatorActivity {
}
}
public void showMessageAndCancelDialog(String error) {
showMessage(error, R.color.secondaryDarkColor);
if (progressDialog != null) {
progressDialog.cancel();
}
}
public void showSuccessAndDismissDialog() {
showMessage(R.string.login_success, R.color.primaryDarkColor);
progressDialog.dismiss();
}
public void emptySensitiveEditFields() {
passwordEdit.setText("");
twoFactorEdit.setText("");
}
public void startMainActivity() {
NavigationBaseActivity.startActivityWithFlags(this, MainActivity.class, Intent.FLAG_ACTIVITY_SINGLE_TOP);
finish();
@ -391,12 +461,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
errorMessageContainer.setVisibility(VISIBLE);
}
private void showMessage(String message, @ColorRes int colorResId) {
errorMessage.setText(message);
errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));
errorMessageContainer.setVisibility(VISIBLE);
}
private AppCompatDelegate getDelegate() {
if (delegate == null) {
delegate = AppCompatDelegate.create(this, null);

View file

@ -1,16 +1,14 @@
package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.wikipedia.login.LoginResult;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
@ -22,6 +20,10 @@ import io.reactivex.Completable;
import io.reactivex.Observable;
import timber.log.Timber;
import static android.accounts.AccountManager.ERROR_CODE_REMOTE_EXCEPTION;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
/**
* Manage the current logged in user session.
*/
@ -32,6 +34,7 @@ public class SessionManager {
private Account currentAccount; // Unlike a savings account... ;-)
private JsonKvStore defaultKvStore;
private static final String KEY_RAWUSERNAME = "rawusername";
private Bundle userdata = new Bundle();
@Inject
public SessionManager(Context context,
@ -43,40 +46,43 @@ public class SessionManager {
this.defaultKvStore = defaultKvStore;
}
private boolean createAccount(@NonNull String userName, @NonNull String password) {
Account account = getCurrentAccount();
if (account == null || TextUtils.isEmpty(account.name) || !account.name.equals(userName)) {
removeAccount();
account = new Account(userName, BuildConfig.ACCOUNT_TYPE);
return accountManager().addAccountExplicitly(account, password, null);
}
return true;
}
/**
* Creata a new account
*
* @param response
* @param username
* @param rawusername
* @param password
*/
public void createAccount(@Nullable AccountAuthenticatorResponse response,
String username, String rawusername, String password) {
private void removeAccount() {
Account account = getCurrentAccount();
if (account != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
accountManager().removeAccountExplicitly(account);
} else {
//noinspection deprecation
accountManager().removeAccount(account, null, null);
Account account = new Account(username, BuildConfig.ACCOUNT_TYPE);
userdata.putString(KEY_RAWUSERNAME, rawusername);
boolean created = accountManager().addAccountExplicitly(account, password, userdata);
Timber.d("account creation " + (created ? "successful" : "failure"));
if (created) {
if (response != null) {
Bundle bundle = new Bundle();
bundle.putString(KEY_ACCOUNT_NAME, username);
bundle.putString(KEY_ACCOUNT_TYPE, BuildConfig.ACCOUNT_TYPE);
response.onResult(bundle);
}
}
}
public void updateAccount(LoginResult result) {
boolean accountCreated = createAccount(result.getUserName(), result.getPassword());
if (accountCreated) {
setPassword(result.getPassword());
} else {
if (response != null) {
response.onError(ERROR_CODE_REMOTE_EXCEPTION, "");
}
Timber.d("account creation failure");
}
}
private void setPassword(@NonNull String password) {
Account account = getCurrentAccount();
if (account != null) {
accountManager().setPassword(account, password);
}
// FIXME: If the user turns it off, it shouldn't be auto turned back on
ContentResolver.setSyncAutomatically(account, BuildConfig.CONTRIBUTION_AUTHORITY, true); // Enable sync by default!
ContentResolver.setSyncAutomatically(account, BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
}
/**
@ -101,7 +107,7 @@ public class SessionManager {
}
@Nullable
private String getRawUserName() {
public String getRawUserName() {
Account account = getCurrentAccount();
return account == null ? null : accountManager().getUserData(account, KEY_RAWUSERNAME);
}
@ -121,12 +127,44 @@ public class SessionManager {
return AccountManager.get(context);
}
public boolean isUserLoggedIn() {
return defaultKvStore.getBoolean("isUserLoggedIn", false);
public Boolean revalidateAuthToken() {
AccountManager accountManager = AccountManager.get(context);
Account curAccount = getCurrentAccount();
if (curAccount == null) {
return false; // This should never happen
}
accountManager.invalidateAuthToken(BuildConfig.ACCOUNT_TYPE, null);
String authCookie = getAuthCookie();
if (authCookie == null) {
return false;
}
mediaWikiApi.setAuthCookie(authCookie);
return true;
}
void setUserLoggedIn(boolean isLoggedIn) {
defaultKvStore.putBoolean("isUserLoggedIn", isLoggedIn);
public String getAuthCookie() {
if (!isUserLoggedIn()) {
Timber.e("User is not logged in");
return null;
} else {
String authCookie = getCachedAuthCookie();
if (authCookie == null) {
Timber.e("Auth cookie is null even after login");
}
return authCookie;
}
}
public String getCachedAuthCookie() {
return defaultKvStore.getString("getAuthCookie", null);
}
public boolean isUserLoggedIn() {
return defaultKvStore.getBoolean("isUserLoggedIn", false);
}
public void forceLogin(Context context) {