mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Enable EmailAuth support.
This commit is contained in:
parent
51da9e4dd6
commit
79a7e5c077
9 changed files with 76 additions and 17 deletions
|
|
@ -65,6 +65,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
||||||
private val delegate: AppCompatDelegate by lazy {
|
private val delegate: AppCompatDelegate by lazy {
|
||||||
AppCompatDelegate.create(this, null)
|
AppCompatDelegate.create(this, null)
|
||||||
}
|
}
|
||||||
|
private var lastLoginResult: LoginResult? = null
|
||||||
|
|
||||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -271,6 +272,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
||||||
showLoggingProgressBar()
|
showLoggingProgressBar()
|
||||||
loginClient.doLogin(username,
|
loginClient.doLogin(username,
|
||||||
password,
|
password,
|
||||||
|
lastLoginResult,
|
||||||
twoFactorCode,
|
twoFactorCode,
|
||||||
Locale.getDefault().language,
|
Locale.getDefault().language,
|
||||||
object : LoginCallback {
|
object : LoginCallback {
|
||||||
|
|
@ -280,9 +282,17 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
||||||
onLoginSuccess(loginResult)
|
onLoginSuccess(loginResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun twoFactorPrompt(caught: Throwable, token: String?) = runOnUiThread {
|
override fun twoFactorPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread {
|
||||||
Timber.d("Requesting 2FA prompt")
|
Timber.d("Requesting 2FA prompt")
|
||||||
progressDialog!!.dismiss()
|
progressDialog!!.dismiss()
|
||||||
|
lastLoginResult = loginResult
|
||||||
|
askUserForTwoFactorAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) {
|
||||||
|
Timber.d("Requesting email auth prompt")
|
||||||
|
progressDialog!!.dismiss()
|
||||||
|
lastLoginResult = loginResult
|
||||||
askUserForTwoFactorAuth()
|
askUserForTwoFactorAuth()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,12 +351,13 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
||||||
progressDialog!!.dismiss()
|
progressDialog!!.dismiss()
|
||||||
with(binding!!) {
|
with(binding!!) {
|
||||||
twoFactorContainer.visibility = View.VISIBLE
|
twoFactorContainer.visibility = View.VISIBLE
|
||||||
|
twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
|
||||||
loginTwoFactor.visibility = View.VISIBLE
|
loginTwoFactor.visibility = View.VISIBLE
|
||||||
loginTwoFactor.requestFocus()
|
loginTwoFactor.requestFocus()
|
||||||
}
|
}
|
||||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
|
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
|
||||||
showMessageAndCancelDialog(R.string.login_failed_2fa_needed)
|
showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed))
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class CsrfTokenClient(
|
||||||
try {
|
try {
|
||||||
if (retry > 0) {
|
if (retry > 0) {
|
||||||
// Log in explicitly
|
// Log in explicitly
|
||||||
loginClient.loginBlocking(userName, password, "")
|
loginClient.loginBlocking(userName, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get CSRFToken response off the main thread.
|
// Get CSRFToken response off the main thread.
|
||||||
|
|
@ -92,6 +92,8 @@ class CsrfTokenClient(
|
||||||
override fun failure(caught: Throwable?) = retryWithLogin(cb) { caught }
|
override fun failure(caught: Throwable?) = retryWithLogin(cb) { caught }
|
||||||
|
|
||||||
override fun twoFactorPrompt() = cb.twoFactorPrompt()
|
override fun twoFactorPrompt() = cb.twoFactorPrompt()
|
||||||
|
|
||||||
|
override fun emailAuthPrompt() = cb.emailAuthPrompt()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -165,10 +167,17 @@ class CsrfTokenClient(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun twoFactorPrompt(
|
override fun twoFactorPrompt(
|
||||||
|
loginResult: LoginResult,
|
||||||
caught: Throwable,
|
caught: Throwable,
|
||||||
token: String?,
|
token: String?,
|
||||||
) = callback.twoFactorPrompt()
|
) = callback.twoFactorPrompt()
|
||||||
|
|
||||||
|
override fun emailAuthPrompt(
|
||||||
|
loginResult: LoginResult,
|
||||||
|
caught: Throwable,
|
||||||
|
token: String?,
|
||||||
|
) = callback.emailAuthPrompt()
|
||||||
|
|
||||||
// Should not happen here, but call the callback just in case.
|
// Should not happen here, but call the callback just in case.
|
||||||
override fun passwordResetPrompt(token: String?) = callback.failure(LoginFailedException("Logged in with temporary password."))
|
override fun passwordResetPrompt(token: String?) = callback.failure(LoginFailedException("Logged in with temporary password."))
|
||||||
|
|
||||||
|
|
@ -190,6 +199,8 @@ class CsrfTokenClient(
|
||||||
fun failure(caught: Throwable?)
|
fun failure(caught: Throwable?)
|
||||||
|
|
||||||
fun twoFactorPrompt()
|
fun twoFactorPrompt()
|
||||||
|
|
||||||
|
fun emailAuthPrompt()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,13 @@ interface LoginCallback {
|
||||||
fun success(loginResult: LoginResult)
|
fun success(loginResult: LoginResult)
|
||||||
|
|
||||||
fun twoFactorPrompt(
|
fun twoFactorPrompt(
|
||||||
|
loginResult: LoginResult,
|
||||||
|
caught: Throwable,
|
||||||
|
token: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun emailAuthPrompt(
|
||||||
|
loginResult: LoginResult,
|
||||||
caught: Throwable,
|
caught: Throwable,
|
||||||
token: String?,
|
token: String?,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.auth.login
|
package fr.free.nrw.commons.auth.login
|
||||||
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult
|
||||||
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
||||||
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
||||||
import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL
|
import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL
|
||||||
|
|
@ -51,6 +52,7 @@ class LoginClient(
|
||||||
password,
|
password,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
response.body()!!.query()!!.loginToken(),
|
response.body()!!.query()!!.loginToken(),
|
||||||
userLanguage,
|
userLanguage,
|
||||||
cb,
|
cb,
|
||||||
|
|
@ -75,6 +77,7 @@ class LoginClient(
|
||||||
password: String,
|
password: String,
|
||||||
retypedPassword: String?,
|
retypedPassword: String?,
|
||||||
twoFactorCode: String?,
|
twoFactorCode: String?,
|
||||||
|
emailAuthCode: String?,
|
||||||
loginToken: String?,
|
loginToken: String?,
|
||||||
userLanguage: String,
|
userLanguage: String,
|
||||||
cb: LoginCallback,
|
cb: LoginCallback,
|
||||||
|
|
@ -82,7 +85,7 @@ class LoginClient(
|
||||||
this.userLanguage = userLanguage
|
this.userLanguage = userLanguage
|
||||||
|
|
||||||
loginCall =
|
loginCall =
|
||||||
if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
|
if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
|
||||||
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
|
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
|
||||||
} else {
|
} else {
|
||||||
loginInterface.postLogIn(
|
loginInterface.postLogIn(
|
||||||
|
|
@ -90,6 +93,7 @@ class LoginClient(
|
||||||
password,
|
password,
|
||||||
retypedPassword,
|
retypedPassword,
|
||||||
twoFactorCode,
|
twoFactorCode,
|
||||||
|
emailAuthCode,
|
||||||
loginToken,
|
loginToken,
|
||||||
userLanguage,
|
userLanguage,
|
||||||
true,
|
true,
|
||||||
|
|
@ -112,10 +116,18 @@ class LoginClient(
|
||||||
when (loginResult) {
|
when (loginResult) {
|
||||||
is OAuthResult ->
|
is OAuthResult ->
|
||||||
cb.twoFactorPrompt(
|
cb.twoFactorPrompt(
|
||||||
|
loginResult,
|
||||||
LoginFailedException(loginResult.message),
|
LoginFailedException(loginResult.message),
|
||||||
loginToken,
|
loginToken,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is EmailAuthResult ->
|
||||||
|
cb.emailAuthPrompt(
|
||||||
|
loginResult,
|
||||||
|
LoginFailedException(loginResult.message),
|
||||||
|
loginToken
|
||||||
|
)
|
||||||
|
|
||||||
is ResetPasswordResult -> cb.passwordResetPrompt(loginToken)
|
is ResetPasswordResult -> cb.passwordResetPrompt(loginToken)
|
||||||
|
|
||||||
is LoginResult.Result ->
|
is LoginResult.Result ->
|
||||||
|
|
@ -147,6 +159,7 @@ class LoginClient(
|
||||||
fun doLogin(
|
fun doLogin(
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
lastLoginResult: LoginResult?,
|
||||||
twoFactorCode: String,
|
twoFactorCode: String,
|
||||||
userLanguage: String,
|
userLanguage: String,
|
||||||
loginCallback: LoginCallback,
|
loginCallback: LoginCallback,
|
||||||
|
|
@ -159,7 +172,10 @@ class LoginClient(
|
||||||
) = if (response.isSuccessful) {
|
) = if (response.isSuccessful) {
|
||||||
val loginToken = response.body()?.query()?.loginToken()
|
val loginToken = response.body()?.query()?.loginToken()
|
||||||
loginToken?.let {
|
loginToken?.let {
|
||||||
login(username, password, null, twoFactorCode, it, userLanguage, loginCallback)
|
login(username, password, null,
|
||||||
|
if (lastLoginResult is OAuthResult) twoFactorCode else null,
|
||||||
|
if (lastLoginResult is EmailAuthResult) twoFactorCode else null,
|
||||||
|
it, userLanguage, loginCallback)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
loginCallback.error(IOException("Failed to retrieve login token"))
|
loginCallback.error(IOException("Failed to retrieve login token"))
|
||||||
}
|
}
|
||||||
|
|
@ -181,7 +197,8 @@ class LoginClient(
|
||||||
fun loginBlocking(
|
fun loginBlocking(
|
||||||
userName: String,
|
userName: String,
|
||||||
password: String,
|
password: String,
|
||||||
twoFactorCode: String?,
|
twoFactorCode: String? = null,
|
||||||
|
emailAuthCode: String? = null
|
||||||
) {
|
) {
|
||||||
val tokenResponse = getLoginToken().execute()
|
val tokenResponse = getLoginToken().execute()
|
||||||
if (tokenResponse
|
if (tokenResponse
|
||||||
|
|
@ -195,7 +212,7 @@ class LoginClient(
|
||||||
|
|
||||||
val loginToken = tokenResponse.body()?.query()?.loginToken()
|
val loginToken = tokenResponse.body()?.query()?.loginToken()
|
||||||
val tempLoginCall =
|
val tempLoginCall =
|
||||||
if (twoFactorCode.isNullOrEmpty()) {
|
if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty()) {
|
||||||
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
|
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
|
||||||
} else {
|
} else {
|
||||||
loginInterface.postLogIn(
|
loginInterface.postLogIn(
|
||||||
|
|
@ -203,6 +220,7 @@ class LoginClient(
|
||||||
password,
|
password,
|
||||||
null,
|
null,
|
||||||
twoFactorCode,
|
twoFactorCode,
|
||||||
|
emailAuthCode,
|
||||||
loginToken,
|
loginToken,
|
||||||
userLanguage,
|
userLanguage,
|
||||||
true,
|
true,
|
||||||
|
|
@ -214,7 +232,7 @@ class LoginClient(
|
||||||
val loginResult = loginResponse.toLoginResult(password) ?: throw IOException("Unexpected response when logging in.")
|
val loginResult = loginResponse.toLoginResult(password) ?: throw IOException("Unexpected response when logging in.")
|
||||||
|
|
||||||
if ("UI" == loginResult.status) {
|
if ("UI" == loginResult.status) {
|
||||||
if (loginResult is OAuthResult) {
|
if (loginResult is OAuthResult || loginResult is EmailAuthResult) {
|
||||||
// TODO: Find a better way to boil up the warning about 2FA
|
// TODO: Find a better way to boil up the warning about 2FA
|
||||||
throw LoginFailedException(loginResult.message)
|
throw LoginFailedException(loginResult.message)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ interface LoginInterface {
|
||||||
@Field("password") pass: String?,
|
@Field("password") pass: String?,
|
||||||
@Field("retype") retypedPass: String?,
|
@Field("retype") retypedPass: String?,
|
||||||
@Field("OATHToken") twoFactorCode: String?,
|
@Field("OATHToken") twoFactorCode: String?,
|
||||||
@Field("logintoken") token: String?,
|
@Field("token") emailAuthToken: String?,
|
||||||
|
@Field("logintoken") loginToken: String?,
|
||||||
@Field("uselang") userLanguage: String?,
|
@Field("uselang") userLanguage: String?,
|
||||||
@Field("logincontinue") loginContinue: Boolean,
|
@Field("logincontinue") loginContinue: Boolean,
|
||||||
): Call<LoginResponse?>
|
): Call<LoginResponse?>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.auth.login
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
||||||
|
import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult
|
||||||
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
||||||
import fr.free.nrw.commons.auth.login.LoginResult.Result
|
import fr.free.nrw.commons.auth.login.LoginResult.Result
|
||||||
import fr.free.nrw.commons.wikidata.mwapi.MwServiceError
|
import fr.free.nrw.commons.wikidata.mwapi.MwServiceError
|
||||||
|
|
@ -27,11 +28,13 @@ internal class ClientLogin {
|
||||||
fun toLoginResult(password: String): LoginResult {
|
fun toLoginResult(password: String): LoginResult {
|
||||||
var userMessage = message
|
var userMessage = message
|
||||||
if ("UI" == status) {
|
if ("UI" == status) {
|
||||||
if (requests != null) {
|
requests?.forEach { request ->
|
||||||
for (req in requests) {
|
request.id()?.let {
|
||||||
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) {
|
if (it.endsWith("TOTPAuthenticationRequest")) {
|
||||||
return OAuthResult(status, userName, password, message)
|
return OAuthResult(status, userName, password, message)
|
||||||
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) {
|
} else if (it.endsWith("EmailAuthAuthenticationRequest")) {
|
||||||
|
return EmailAuthResult(status, userName, password, message)
|
||||||
|
} else if (it.endsWith("PasswordAuthenticationRequest")) {
|
||||||
return ResetPasswordResult(status, userName, password, message)
|
return ResetPasswordResult(status, userName, password, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,7 +52,7 @@ internal class Request {
|
||||||
private val required: String? = null
|
private val required: String? = null
|
||||||
private val provider: String? = null
|
private val provider: String? = null
|
||||||
private val account: String? = null
|
private val account: String? = null
|
||||||
private val fields: Map<String, RequestField>? = null
|
internal val fields: Map<String, RequestField>? = null
|
||||||
|
|
||||||
fun id(): String? = id
|
fun id(): String? = id
|
||||||
}
|
}
|
||||||
|
|
@ -57,5 +60,5 @@ internal class Request {
|
||||||
internal class RequestField {
|
internal class RequestField {
|
||||||
private val type: String? = null
|
private val type: String? = null
|
||||||
private val label: String? = null
|
private val label: String? = null
|
||||||
private val help: String? = null
|
internal val help: String? = null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ sealed class LoginResult(
|
||||||
message: String?,
|
message: String?,
|
||||||
) : LoginResult(status, userName, password, message)
|
) : LoginResult(status, userName, password, message)
|
||||||
|
|
||||||
|
class EmailAuthResult(
|
||||||
|
status: String,
|
||||||
|
userName: String?,
|
||||||
|
password: String?,
|
||||||
|
message: String?,
|
||||||
|
) : LoginResult(status, userName, password, message)
|
||||||
|
|
||||||
class ResetPasswordResult(
|
class ResetPasswordResult(
|
||||||
status: String,
|
status: String,
|
||||||
userName: String?,
|
userName: String?,
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
android:layout_marginEnd="@dimen/standard_gap"
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
android:layout_marginRight="@dimen/standard_gap"
|
||||||
android:layout_marginBottom="@dimen/standard_gap"
|
android:layout_marginBottom="@dimen/standard_gap"
|
||||||
|
android:hint="@string/_2fa_code"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:passwordToggleEnabled="false"
|
app:passwordToggleEnabled="false"
|
||||||
tools:visibility="visible">
|
tools:visibility="visible">
|
||||||
|
|
@ -154,9 +155,7 @@
|
||||||
android:id="@+id/login_two_factor"
|
android:id="@+id/login_two_factor"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/_2fa_code"
|
|
||||||
android:imeOptions="flagNoExtractUi"
|
android:imeOptions="flagNoExtractUi"
|
||||||
android:inputType="number"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@
|
||||||
<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_needed">You must provide your two factor authentication code.</string>
|
<string name="login_failed_2fa_needed">You must provide your two factor authentication code.</string>
|
||||||
|
<string name="login_failed_email_auth_needed">A login verification code has been sent to your email address. Please provide the code to log in.</string>
|
||||||
<string name="login_failed_generic">Log-in failed</string>
|
<string name="login_failed_generic">Log-in 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>
|
||||||
|
|
@ -218,6 +219,7 @@
|
||||||
<string name="become_a_tester_description">Opt-in to our beta channel on Google Play and get early access to new features and bug fixes</string>
|
<string name="become_a_tester_description">Opt-in to our beta channel on Google Play and get early access to new features and bug fixes</string>
|
||||||
<string name="beta_opt_in_link">https://play.google.com/apps/testing/fr.free.nrw.commons</string>
|
<string name="beta_opt_in_link">https://play.google.com/apps/testing/fr.free.nrw.commons</string>
|
||||||
<string name="_2fa_code">2FA Code</string>
|
<string name="_2fa_code">2FA Code</string>
|
||||||
|
<string name="email_auth_code">Email verification code</string>
|
||||||
<string name="logout_verification">Do you really want to logout?</string>
|
<string name="logout_verification">Do you really want to logout?</string>
|
||||||
<string name="mediaimage_failed">Media Image Failed</string>
|
<string name="mediaimage_failed">Media Image Failed</string>
|
||||||
<string name="no_subcategory_found">No subcategories found</string>
|
<string name="no_subcategory_found">No subcategories found</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue