Skip to content

Create CsrfTokenClient and LoginClient by injection #5491

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Inject
SessionManager sessionManager;

@Inject
@Named(NAMED_COMMONS_WIKI_SITE)
WikiSite commonsWikiSite;

@Inject
@Named("default_preferences")
JsonKvStore applicationKvStore;
Expand Down Expand Up @@ -231,13 +227,13 @@ public void performLogin() {

private void doLogin(String username, String password, String twoFactorCode) {
progressDialog.show();
loginToken = ServiceFactory.get(commonsWikiSite, LoginInterface.class).getLoginToken();
loginToken = loginClient.getLoginToken();
loginToken.enqueue(
new Callback<MwQueryResponse>() {
@Override
public void onResponse(Call<MwQueryResponse> call,
Response<MwQueryResponse> response) {
loginClient.login(commonsWikiSite, username, password, null, twoFactorCode,
loginClient.login(username, password, null, twoFactorCode,
response.body().query().loginToken(), Locale.getDefault().getLanguage(), new LoginCallback() {
@Override
public void success(@NonNull LoginResult result) {
Expand Down
19 changes: 7 additions & 12 deletions app/src/main/java/fr/free/nrw/commons/auth/csrf/CsrfTokenClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package fr.free.nrw.commons.auth.csrf
import androidx.annotation.VisibleForTesting
import fr.free.nrw.commons.auth.SessionManager
import org.wikipedia.AppAdapter
import org.wikipedia.dataclient.ServiceFactory
import org.wikipedia.dataclient.SharedPreferenceCookieManager
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import fr.free.nrw.commons.auth.login.LoginClient
import fr.free.nrw.commons.auth.login.LoginCallback
Expand All @@ -19,31 +17,29 @@ import java.util.concurrent.Callable
import java.util.concurrent.Executors.newSingleThreadExecutor

class CsrfTokenClient(
private val csrfWikiSite: WikiSite,
private val sessionManager: SessionManager
private val sessionManager: SessionManager,
private val csrfTokenInterface: CsrfTokenInterface,
private val loginClient: LoginClient
) {
private var retries = 0
private var csrfTokenCall: Call<MwQueryResponse?>? = null
private val loginClient = LoginClient()

@Throws(Throwable::class)
fun getTokenBlocking(): String {
var token = ""
val service = ServiceFactory.get(csrfWikiSite, CsrfTokenInterface::class.java)
val userName = AppAdapter.get().getUserName()
val password = AppAdapter.get().getPassword()

for (retry in 0 until MAX_RETRIES_OF_LOGIN_BLOCKING) {
try {
if (retry > 0) {
// Log in explicitly
LoginClient()
.loginBlocking(csrfWikiSite, userName, password, "")
loginClient.loginBlocking(userName, password, "")
}

// Get CSRFToken response off the main thread.
val response = newSingleThreadExecutor().submit(Callable {
service.getCsrfTokenCall().execute()
csrfTokenInterface.getCsrfTokenCall().execute()
}).get()

if (response.body()?.query()?.csrfToken().isNullOrEmpty()) {
Expand Down Expand Up @@ -114,7 +110,7 @@ class CsrfTokenClient(
login(userName, password, callback) {
Timber.i("retrying...")
cancel()
csrfTokenCall = request(ServiceFactory.get(csrfWikiSite, CsrfTokenInterface::class.java), callback)
csrfTokenCall = request(csrfTokenInterface, callback)
}
} else {
callback.failure(caught())
Expand All @@ -126,8 +122,7 @@ class CsrfTokenClient(
password: String,
callback: Callback,
retryCallback: () -> Unit
) = LoginClient()
.request(csrfWikiSite, username, password, object : LoginCallback {
) = loginClient.request(username, password, object : LoginCallback {
override fun success(loginResult: LoginResult) {
if (loginResult.pass) {
sessionManager.updateAccount(loginResult)
Expand Down
69 changes: 32 additions & 37 deletions app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.wikipedia.dataclient.ServiceFactory
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import retrofit2.Call
import retrofit2.Callback
Expand All @@ -18,7 +16,7 @@ import java.io.IOException
/**
* Responsible for making login related requests to the server.
*/
class LoginClient {
class LoginClient(private val loginInterface: LoginInterface) {
private var tokenCall: Call<MwQueryResponse?>? = null
private var loginCall: Call<LoginResponse?>? = null

Expand All @@ -30,14 +28,18 @@ class LoginClient {
*/
private var userLanguage = ""

fun request(wiki: WikiSite, userName: String, password: String, cb: LoginCallback) {
fun getLoginToken() = loginInterface.getLoginToken()

fun request(userName: String, password: String, cb: LoginCallback) {
cancel()

tokenCall = ServiceFactory.get(wiki, LoginInterface::class.java).getLoginToken()
tokenCall = getLoginToken()
tokenCall!!.enqueue(object : Callback<MwQueryResponse?> {
override fun onResponse(call: Call<MwQueryResponse?>, response: Response<MwQueryResponse?>) {
login(wiki, userName, password, null, null,
response.body()!!.query()!!.loginToken(), userLanguage, cb)
login(
userName, password, null, null, response.body()!!.query()!!.loginToken(),
userLanguage, cb
)
}

override fun onFailure(call: Call<MwQueryResponse?>, caught: Throwable) {
Expand All @@ -50,16 +52,15 @@ class LoginClient {
}

fun login(
wiki: WikiSite, userName: String, password: String, retypedPassword: String?,
twoFactorCode: String?, loginToken: String?, userLanguage: String, cb: LoginCallback
userName: String, password: String, retypedPassword: String?, twoFactorCode: String?,
loginToken: String?, userLanguage: String, cb: LoginCallback
) {
this.userLanguage = userLanguage

loginCall = if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
ServiceFactory.get(wiki, LoginInterface::class.java)
.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
} else {
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
loginInterface.postLogIn(
userName, password, retypedPassword, twoFactorCode, loginToken, userLanguage, true
)
}
Expand All @@ -69,12 +70,12 @@ class LoginClient {
call: Call<LoginResponse?>,
response: Response<LoginResponse?>
) {
val loginResult = response.body()?.toLoginResult(wiki, password)
val loginResult = response.body()?.toLoginResult(password)
if (loginResult != null) {
if (loginResult.pass && !loginResult.userName.isNullOrEmpty()) {
// The server could do some transformations on user names, e.g. on some
// wikis is uppercases the first letter.
getExtendedInfo(wiki, loginResult.userName, loginResult, cb)
getExtendedInfo(loginResult.userName, loginResult, cb)
} else if ("UI" == loginResult.status) {
when (loginResult) {
is OAuthResult -> cb.twoFactorPrompt(
Expand Down Expand Up @@ -106,25 +107,24 @@ class LoginClient {
}

@Throws(Throwable::class)
fun loginBlocking(wiki: WikiSite, userName: String, password: String, twoFactorCode: String?) {
val tokenResponse = ServiceFactory.get(wiki, LoginInterface::class.java).getLoginToken().execute()
fun loginBlocking(userName: String, password: String, twoFactorCode: String?) {
val tokenResponse = getLoginToken().execute()
if (tokenResponse.body()?.query()?.loginToken().isNullOrEmpty()) {
throw IOException("Unexpected response when getting login token.")
}

val loginToken = tokenResponse.body()?.query()?.loginToken()
val tempLoginCall = if (twoFactorCode.isNullOrEmpty()) {
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
} else {
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
loginInterface.postLogIn(
userName, password, null, twoFactorCode, loginToken, userLanguage, true
)
}

val response = tempLoginCall.execute()
val loginResponse = response.body() ?: throw IOException("Unexpected response when logging in.")
val loginResult = loginResponse.toLoginResult(wiki, 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 (loginResult is OAuthResult) {
Expand All @@ -139,23 +139,18 @@ class LoginClient {
}
}

private fun getExtendedInfo(
wiki: WikiSite, userName: String, loginResult: LoginResult, cb: LoginCallback
) = ServiceFactory.get(wiki, LoginInterface::class.java).getUserInfo(userName)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe({ response: MwQueryResponse? ->
loginResult.userId = response?.query()?.userInfo()?.id() ?: 0
loginResult.groups = response?.query()?.getUserResponse(userName)?.groups ?: emptySet()
cb.success(loginResult)
Timber.v(
"Found user ID %s for %s",
response?.query()?.userInfo()?.id(),
wiki.subdomain()
)
}, { caught: Throwable ->
Timber.e(caught, "Login succeeded but getting group information failed. ")
cb.error(caught)
})
private fun getExtendedInfo(userName: String, loginResult: LoginResult, cb: LoginCallback) =
loginInterface.getUserInfo(userName)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe({ response: MwQueryResponse? ->
loginResult.userId = response?.query()?.userInfo()?.id() ?: 0
loginResult.groups =
response?.query()?.getUserResponse(userName)?.groups ?: emptySet()
cb.success(loginResult)
}, { caught: Throwable ->
Timber.e(caught, "Login succeeded but getting group information failed. ")
cb.error(caught)
})

fun cancel() {
tokenCall?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.google.gson.annotations.SerializedName
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.Result
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwServiceError

class LoginResponse {
Expand All @@ -14,8 +13,8 @@ class LoginResponse {
@SerializedName("clientlogin")
private val clientLogin: ClientLogin? = null

fun toLoginResult(site: WikiSite, password: String): LoginResult? {
return clientLogin?.toLoginResult(site, password)
fun toLoginResult(password: String): LoginResult? {
return clientLogin?.toLoginResult(password)
}
}

Expand All @@ -27,23 +26,23 @@ internal class ClientLogin {
@SerializedName("username")
private val userName: String? = null

fun toLoginResult(site: WikiSite, password: String): LoginResult {
fun toLoginResult(password: String): LoginResult {
var userMessage = message
if ("UI" == status) {
if (requests != null) {
for (req in requests) {
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) {
return OAuthResult(site, status, userName, password, message)
return OAuthResult(status, userName, password, message)
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) {
return ResetPasswordResult(site, status, userName, password, message)
return ResetPasswordResult(status, userName, password, message)
}
}
}
} else if ("PASS" != status && "FAIL" != status) {
//TODO: String resource -- Looks like needed for others in this class too
userMessage = "An unknown error occurred."
}
return Result(site, status ?: "", userName, password, userMessage)
return Result(status ?: "", userName, password, userMessage)
}
}

Expand Down
12 changes: 3 additions & 9 deletions app/src/main/java/fr/free/nrw/commons/auth/login/LoginResult.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package fr.free.nrw.commons.auth.login

import org.wikipedia.dataclient.WikiSite

sealed class LoginResult(
val site: WikiSite,
val status: String,
val userName: String?,
val password: String?,
Expand All @@ -14,26 +11,23 @@ sealed class LoginResult(
val pass: Boolean get() = "PASS" == status

class Result(
site: WikiSite,
status: String,
userName: String?,
password: String?,
message: String?
): LoginResult(site, status, userName, password, message)
): LoginResult(status, userName, password, message)

class OAuthResult(
site: WikiSite,
status: String,
userName: String?,
password: String?,
message: String?
) : LoginResult(site, status, userName, password, message)
) : LoginResult(status, userName, password, message)

class ResetPasswordResult(
site: WikiSite,
status: String,
userName: String?,
password: String?,
message: String?
) : LoginResult(site, status, userName, password, message)
) : LoginResult(status, userName, password, message)
}
25 changes: 19 additions & 6 deletions app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import fr.free.nrw.commons.actions.PageEditInterface;
import fr.free.nrw.commons.actions.ThanksInterface;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.CsrfTokenInterface;
import fr.free.nrw.commons.auth.login.LoginInterface;
import fr.free.nrw.commons.category.CategoryInterface;
import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.kvstore.JsonKvStore;
Expand All @@ -37,7 +39,6 @@
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient;
import org.wikipedia.dataclient.Service;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.json.GsonUtil;
Expand Down Expand Up @@ -106,15 +107,27 @@ public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient,
@Named(NAMED_COMMONS_CSRF)
@Provides
@Singleton
public CsrfTokenClient provideCommonsCsrfTokenClient(
@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite, SessionManager sessionManager) {
return new CsrfTokenClient(commonsWikiSite, sessionManager);
public CsrfTokenClient provideCommonsCsrfTokenClient(SessionManager sessionManager,
CsrfTokenInterface tokenInterface, LoginClient loginClient) {
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient);
}

@Provides
@Singleton
public LoginClient provideLoginClient() {
return new LoginClient();
public CsrfTokenInterface provideCsrfTokenInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, CsrfTokenInterface.class);
}

@Provides
@Singleton
public LoginInterface provideLoginInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, LoginInterface.class);
}

@Provides
@Singleton
public LoginClient provideLoginClient(LoginInterface loginInterface) {
return new LoginClient(loginInterface);
}

@Provides
Expand Down
Loading