mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
parent
1d0dad9e1a
commit
ea051c119a
5 changed files with 106 additions and 29 deletions
|
|
@ -10,6 +10,9 @@ import org.mediawiki.api.ApiResult;
|
||||||
*/
|
*/
|
||||||
public class MWApi extends org.mediawiki.api.MWApi {
|
public class MWApi extends org.mediawiki.api.MWApi {
|
||||||
|
|
||||||
|
/** We don't actually use this but need to pass it in requests */
|
||||||
|
private static String LOGIN_RETURN_TO_URL = "https://commons.wikimedia.org";
|
||||||
|
|
||||||
public MWApi(String apiURL, AbstractHttpClient client) {
|
public MWApi(String apiURL, AbstractHttpClient client) {
|
||||||
super(apiURL, client);
|
super(apiURL, client);
|
||||||
}
|
}
|
||||||
|
|
@ -17,41 +20,69 @@ public class MWApi extends org.mediawiki.api.MWApi {
|
||||||
/**
|
/**
|
||||||
* @param username String
|
* @param username String
|
||||||
* @param password String
|
* @param password String
|
||||||
* @return String On success: "PASS"
|
* @return String as returned by this.getErrorCodeToReturn()
|
||||||
* continue: "2FA" (More information required for 2FA)
|
|
||||||
* failure: A failure message code (defined by mediawiki)
|
|
||||||
* misc: genericerror-UI, genericerror-REDIRECT, genericerror-RESTART
|
|
||||||
* @throws IOException On api request IO issue
|
* @throws IOException On api request IO issue
|
||||||
*/
|
*/
|
||||||
public String login(String username, String password) throws IOException {
|
public String login(String username, String password) throws IOException {
|
||||||
|
String token = this.getLoginToken();
|
||||||
/** Request a login token to be used later to log in. */
|
ApiResult loginApiResult = this.action("clientlogin")
|
||||||
ApiResult tokenData = this.action("query")
|
|
||||||
.param("action", "query")
|
|
||||||
.param("meta", "tokens")
|
|
||||||
.param("type", "login")
|
|
||||||
.post();
|
|
||||||
String token = tokenData.getString("/api/query/tokens/@logintoken");
|
|
||||||
|
|
||||||
/** Actually log in. */
|
|
||||||
ApiResult loginData = this.action("clientlogin")
|
|
||||||
.param("rememberMe", "1")
|
.param("rememberMe", "1")
|
||||||
.param("username", username)
|
.param("username", username)
|
||||||
.param("password", password)
|
.param("password", password)
|
||||||
.param("logintoken", token)
|
.param("logintoken", token)
|
||||||
.param("loginreturnurl", "http://example.com/")//TODO return to url?
|
.param("loginreturnurl", LOGIN_RETURN_TO_URL)
|
||||||
.post();
|
.post();
|
||||||
String status = loginData.getString("/api/clientlogin/@status");
|
return this.getErrorCodeToReturn( loginApiResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username String
|
||||||
|
* @param password String
|
||||||
|
* @param twoFactorCode String
|
||||||
|
* @return String as returned by this.getErrorCodeToReturn()
|
||||||
|
* @throws IOException On api request IO issue
|
||||||
|
*/
|
||||||
|
public String login(String username, String password, String twoFactorCode) throws IOException {
|
||||||
|
String token = this.getLoginToken();//TODO cache this instead of calling again when 2FAing
|
||||||
|
ApiResult loginApiResult = this.action("clientlogin")
|
||||||
|
.param("rememberMe", "1")
|
||||||
|
.param("username", username)
|
||||||
|
.param("password", password)
|
||||||
|
.param("logintoken", token)
|
||||||
|
.param("logincontinue", "1")
|
||||||
|
.param("OATHToken", twoFactorCode)
|
||||||
|
.post();
|
||||||
|
|
||||||
|
return this.getErrorCodeToReturn( loginApiResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLoginToken() throws IOException {
|
||||||
|
ApiResult tokenResult = this.action("query")
|
||||||
|
.param("action", "query")
|
||||||
|
.param("meta", "tokens")
|
||||||
|
.param("type", "login")
|
||||||
|
.post();
|
||||||
|
return tokenResult.getString("/api/query/tokens/@logintoken");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loginApiResult ApiResult Any clientlogin api result
|
||||||
|
* @return String On success: "PASS"
|
||||||
|
* continue: "2FA" (More information required for 2FA)
|
||||||
|
* failure: A failure message code (defined by mediawiki)
|
||||||
|
* misc: genericerror-UI, genericerror-REDIRECT, genericerror-RESTART
|
||||||
|
*/
|
||||||
|
private String getErrorCodeToReturn( ApiResult loginApiResult ) {
|
||||||
|
String status = loginApiResult.getString("/api/clientlogin/@status");
|
||||||
if (status.equals("PASS")) {
|
if (status.equals("PASS")) {
|
||||||
this.isLoggedIn = true;
|
this.isLoggedIn = true;
|
||||||
return status;
|
return status;
|
||||||
} else if (status.equals("FAIL")) {
|
} else if (status.equals("FAIL")) {
|
||||||
return loginData.getString("/api/clientlogin/@messagecode");
|
return loginApiResult.getString("/api/clientlogin/@messagecode");
|
||||||
} else if (
|
} else if (
|
||||||
status.equals("UI")
|
status.equals("UI")
|
||||||
&& loginData.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
|
&& loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
|
||||||
&& loginData.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
|
&& loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
|
||||||
) {
|
) {
|
||||||
return "2FA";
|
return "2FA";
|
||||||
}
|
}
|
||||||
|
|
@ -60,5 +91,4 @@ public class MWApi extends org.mediawiki.api.MWApi {
|
||||||
return "genericerror-" + status;
|
return "genericerror-" + status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
Button signupButton;
|
Button signupButton;
|
||||||
EditText usernameEdit;
|
EditText usernameEdit;
|
||||||
EditText passwordEdit;
|
EditText passwordEdit;
|
||||||
|
EditText twoFactorEdit;
|
||||||
ProgressDialog dialog;
|
ProgressDialog dialog;
|
||||||
|
|
||||||
private class LoginTask extends AsyncTask<String, String, String> {
|
private class LoginTask extends AsyncTask<String, String, String> {
|
||||||
|
|
@ -55,6 +56,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
Activity context;
|
Activity context;
|
||||||
String username;
|
String username;
|
||||||
String password;
|
String password;
|
||||||
|
String twoFactorCode = "";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(String result) {
|
protected void onPostExecute(String result) {
|
||||||
|
|
@ -107,12 +109,11 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
|
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
|
||||||
// Matches nosuchuser, nosuchusershort, noname
|
// Matches nosuchuser, nosuchusershort, noname
|
||||||
response = R.string.login_failed_username;
|
response = R.string.login_failed_username;
|
||||||
passwordEdit.setText("");
|
emptySensitiveEditFields();
|
||||||
|
|
||||||
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
|
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
|
||||||
// Matches wrongpassword, wrongpasswordempty
|
// Matches wrongpassword, wrongpasswordempty
|
||||||
response = R.string.login_failed_password;
|
response = R.string.login_failed_password;
|
||||||
passwordEdit.setText("");
|
emptySensitiveEditFields();
|
||||||
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
|
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
|
||||||
// Matches unknown throttle error codes
|
// Matches unknown throttle error codes
|
||||||
response = R.string.login_failed_throttled;
|
response = R.string.login_failed_throttled;
|
||||||
|
|
@ -120,7 +121,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
// Matches login-userblocked
|
// Matches login-userblocked
|
||||||
response = R.string.login_failed_blocked;
|
response = R.string.login_failed_blocked;
|
||||||
} else if (result.equals("2FA")){
|
} else if (result.equals("2FA")){
|
||||||
response = R.string.login_failed_2fa_not_supported;
|
twoFactorEdit.setVisibility(View.VISIBLE);
|
||||||
|
response = R.string.login_failed_2fa_needed;
|
||||||
} else {
|
} else {
|
||||||
// Occurs with unhandled login failure codes
|
// Occurs with unhandled login failure codes
|
||||||
Timber.d("Login failed with reason: %s", result);
|
Timber.d("Login failed with reason: %s", result);
|
||||||
|
|
@ -131,6 +133,11 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void emptySensitiveEditFields() {
|
||||||
|
passwordEdit.setText("");
|
||||||
|
twoFactorEdit.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
|
|
@ -150,8 +157,16 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
protected String doInBackground(String... params) {
|
protected String doInBackground(String... params) {
|
||||||
username = params[0];
|
username = params[0];
|
||||||
password = params[1];
|
password = params[1];
|
||||||
|
if(params.length > 2) {
|
||||||
|
twoFactorCode = params[2];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return app.getApi().login(username, password);
|
if(twoFactorCode.isEmpty()) {
|
||||||
|
return app.getApi().login(username, password);
|
||||||
|
} else {
|
||||||
|
return app.getApi().login(username, password, twoFactorCode);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Do something better!
|
// Do something better!
|
||||||
return "NetworkFailure";
|
return "NetworkFailure";
|
||||||
|
|
@ -168,6 +183,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
signupButton = (Button) findViewById(R.id.signupButton);
|
signupButton = (Button) findViewById(R.id.signupButton);
|
||||||
usernameEdit = (EditText) findViewById(R.id.loginUsername);
|
usernameEdit = (EditText) findViewById(R.id.loginUsername);
|
||||||
passwordEdit = (EditText) findViewById(R.id.loginPassword);
|
passwordEdit = (EditText) findViewById(R.id.loginPassword);
|
||||||
|
twoFactorEdit = (EditText) findViewById(R.id.loginTwoFactor);
|
||||||
final LoginActivity that = this;
|
final LoginActivity that = this;
|
||||||
|
|
||||||
prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
|
prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
|
||||||
|
|
@ -181,7 +197,11 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable editable) {
|
public void afterTextChanged(Editable editable) {
|
||||||
if(usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0) {
|
if(
|
||||||
|
usernameEdit.getText().length() != 0 &&
|
||||||
|
passwordEdit.getText().length() != 0 &&
|
||||||
|
( twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE )
|
||||||
|
) {
|
||||||
loginButton.setEnabled(true);
|
loginButton.setEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
loginButton.setEnabled(false);
|
loginButton.setEnabled(false);
|
||||||
|
|
@ -191,6 +211,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
usernameEdit.addTextChangedListener(loginEnabler);
|
usernameEdit.addTextChangedListener(loginEnabler);
|
||||||
passwordEdit.addTextChangedListener(loginEnabler);
|
passwordEdit.addTextChangedListener(loginEnabler);
|
||||||
|
twoFactorEdit.addTextChangedListener(loginEnabler);
|
||||||
passwordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
passwordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
||||||
|
|
@ -249,9 +270,15 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
String password = passwordEdit.getText().toString();
|
String password = passwordEdit.getText().toString();
|
||||||
|
|
||||||
|
String twoFactorCode = twoFactorEdit.getText().toString();
|
||||||
|
|
||||||
Timber.d("Login to start!");
|
Timber.d("Login to start!");
|
||||||
LoginTask task = new LoginTask(this);
|
LoginTask task = new LoginTask(this);
|
||||||
task.execute(canonicalUsername, password);
|
if(twoFactorCode.isEmpty()) {
|
||||||
|
task.execute(canonicalUsername, password);
|
||||||
|
} else {
|
||||||
|
task.execute(canonicalUsername, password, twoFactorCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||||
|
|
||||||
private String getAuthCookie(String username, String password) throws IOException {
|
private String getAuthCookie(String username, String password) throws IOException {
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.createMWApi();
|
||||||
|
//TODO add 2fa support here
|
||||||
String result = api.login(username, password);
|
String result = api.login(username, password);
|
||||||
if(result.equals("PASS")) {
|
if(result.equals("PASS")) {
|
||||||
return api.getAuthCookie();
|
return api.getAuthCookie();
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,24 @@
|
||||||
|
|
||||||
</android.support.design.widget.TextInputLayout>
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:passwordToggleEnabled="false"
|
||||||
|
>
|
||||||
|
|
||||||
|
<android.support.design.widget.TextInputEditText
|
||||||
|
android:id="@+id/loginTwoFactor"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/_2fa_code"
|
||||||
|
android:imeOptions="flagNoExtractUi"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:visibility="gone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</android.support.design.widget.TextInputLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/loginButton"
|
android:id="@+id/loginButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
<string name="login_failed_password">Unable to login - please check your password</string>
|
<string name="login_failed_password">Unable to login - please check your password</string>
|
||||||
<string name="login_failed_throttled">Too many unsuccessful attempts. Please try again in a few minutes.</string>
|
<string name="login_failed_throttled">Too many unsuccessful attempts. Please try again in a few minutes.</string>
|
||||||
<string name="login_failed_blocked">Sorry, this user has been blocked on Commons</string>
|
<string name="login_failed_blocked">Sorry, this user has been blocked on Commons</string>
|
||||||
<string name="login_failed_2fa_not_supported">The app doesn\'t currently support 2 Factor Authentication.</string>
|
<string name="login_failed_2fa_needed">You must provide your two factor authentication code.</string>
|
||||||
<string name="login_failed_generic">Login failed</string>
|
<string name="login_failed_generic">Login failed</string>
|
||||||
<string name="share_upload_button">Upload</string>
|
<string name="share_upload_button">Upload</string>
|
||||||
<string name="multiple_share_base_title">Name this set</string>
|
<string name="multiple_share_base_title">Name this set</string>
|
||||||
|
|
@ -173,6 +173,7 @@ Tap this message (or hit back) to skip this step.</string>
|
||||||
<string name="map_theme_light">mapbox://styles/mapbox/traffic-day-v2</string>
|
<string name="map_theme_light">mapbox://styles/mapbox/traffic-day-v2</string>
|
||||||
<string name="map_theme_dark">mapbox://styles/mapbox/traffic-night-v2</string>
|
<string name="map_theme_dark">mapbox://styles/mapbox/traffic-night-v2</string>
|
||||||
<string name="mapbox_commons_app_token">pk.eyJ1IjoibWFza2FyYXZpdmVrIiwiYSI6ImNqMmxvdzFjMTAwMHYzM283ZWM3eW5tcDAifQ.ib5SZ9EVjwJe6GSKve0bcg</string>
|
<string name="mapbox_commons_app_token">pk.eyJ1IjoibWFza2FyYXZpdmVrIiwiYSI6ImNqMmxvdzFjMTAwMHYzM283ZWM3eW5tcDAifQ.ib5SZ9EVjwJe6GSKve0bcg</string>
|
||||||
|
<string name="_2fa_code">2FA Code</string>
|
||||||
<string name="number_of_uploads">My Recent Upload Limit</string>
|
<string name="number_of_uploads">My Recent Upload Limit</string>
|
||||||
<string name="maximum_limit">Maximum Limit</string>
|
<string name="maximum_limit">Maximum Limit</string>
|
||||||
<string name="maximum_limit_alert">Maximum limit should be 500</string>
|
<string name="maximum_limit_alert">Maximum limit should be 500</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue